Your Singleton Might Not Be "Single"

Singleton has been a very popular design pattern as it is proven to have some advantages compared to the global variables. The Gang of four books listed the following benefits of using singleton,

  1. Controlled access to the sole instance.
  2. Reduced namespace.
  3. Permits refinement of operations and representation.
  4. Permits a variable number of instances.
  5. More flexible than class operations.

Web application developers have often found it useful as a form of caching mechanism, using it to hold some data that needs to be accessed so often that fetching it from the database might be a performance overhead. The example of that might be an access token for API authorization.

But Singleton has often been described as an AntiPattern mainly because of its unpredictable nature. The primary properties of a singleton are

  1. There must be exactly one instance of a class.
  2. The single instance must be accessible to clients from a well-known access point.

The implication of the first property is that a singleton class must be properly implemented such that multiple instances of that class should not exist. This is usually not the case as there are many reasons why multiple instances of your singleton class might exist. Here are some reasons,

  1. Multi-Threading - If your singleton class is not properly implemented with lock and synchronization, then when it is accessed from multiple threads, different instances would be created.
  2. Class loaders - If your singleton is loaded with different class loaders, then it would have multiple instances.
  3. Shared Server - If your application is running on a shared server, then different instances of your application can be spurned to serve your requests.

The Simply Singleton article describes how you can overcome the problems 1 and 2 above but those methods have their own implications. On the other hand, problem 3 is the major reason why I would not recommend using a singleton to hold the data that is important to the business logic of your application.

Recently, I was working on a project where the authorization token to access the APIs for different users was stored in a singleton class like this,

  1. /** 
  2. This class has been simplified for this article 
  3. **/  
  4. public enum TokenManager implements Constants {  
  5.  INSTANCE;  
  6.  private ConcurrentHashMap<String, LoggedInUser> tokenUserMap = new ConcurrentHashMap<>();  
  7.   
  8. public void addToken(String token, User user) {  
  9.  tokenUserMap.put(token, new LoggedInUser(user));  
  10.  }  
  11.   
  12. public void updateLastAccessedTime(String token) {  
  13.  tokenUserMap.get(token).setTime(new DateTime());  
  14.  }  
  15.   
  16. public boolean isTokenAbsent(String token) {  
  17.  return tokenUserMap.get(token) == null;  
  18.  }  
  19. }  

The ConcurrentHashMap is used to store the user token information and then, since it is assumed that there is just one instance of this class, the hashmap would always give the correct data for authorization.

The bug in this code is not obvious when a single instance of your application is running (this was our case as we had configured our Google App Engine application to use just one instance in our test environment because it was running in the flexible environment where Google does not automatically manage the instance up and downtime). But after we allowed multiple instances of our application to run at the same time, we had multiple instances of our singleton class. So, while the access token might be set in INSTANCE1, the instance serving our request might be INSTANCE2 and then the client would get the error that the token is not found on the server. In some cloud frameworks, like Google App Engine standard environment, when your application is not serving the request, its instances could be shut down dynamically to save the cost. So, when your singleton class is re-instantiated, then all the data would be gone.

Thus, if you are not absolutely sure that your singleton would have one and only one instance, you should never use it to handle the data that has to do with the business logic of your application.


Similar Articles