Running on Java 24-ea+24-2960 (Preview)
Home of The JavaSpecialists' Newsletter

052J2EE Singleton

Author: David Jones (Virgin Mobile USA)Date: 2002-07-02Java Version: 1.3Category: Software Engineering
 

Abstract: Singleton is one of the most recognizable design pattern from the Gang of Four book. We read how David Jones applies it to J2EE.

 

Welcome to the 52nd edition of The Java(tm) Specialists' Newsletter sent to almost 4000 Java experts in 84 countries. Any day now we will count over to the 4000 mark! In countries besides my home country South Africa I would now get a bottle of champagne ready, but South Africa is one of only 12 countries where you can actually drink the water from the tap, and we are ranked #3 in terms of tapwater in the world - so I think I'll just drink a glass of fresh clear delicious South African water to celebrate the landmark 4000th subscriber :-)

This week I am very pleased to welcome a new contributor, David Jones, onto my newsletter. David is based in San Francisco, working for Virgin Mobile USA as a J2EE Architecture consultant. If you have an extremely lucrative job in that area, for someone with 4 years solid Java experience, you are welcome to contact David, although I don't know if he is actively looking for new challenges or not ;-) I might add that David is actually of British descent, so he knows how to do his maths and draw with colour.

I know that this is not the first newsletter in the world to speak about the Singleton pattern and its role in J2EE. I do hope, however, that it will show a different angle to what others have done.

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

J2EE Singleton

What is a Singleton?

The Singleton pattern became widely popular when it was included in the now famous "Gang of Four" Patterns book [hk: see the brochure of my design patterns course for information about several patterns]. Its general objective is to make sure that only one instance of a specific object type exists.

The classic code snippet used to create a Singleton is as follows

public class Singleton1 {
  private static Singleton1 instance = null;
  private Singleton1() {}
  public static synchronized Singleton1 getInstance() {
    if (instance == null)
      instance = new Singleton1();
    return instance;
  }
}

Some examples of a Singleton use the "Double Checked Locking method" to create the Singleton.

public class Singleton2 {
  private static Singleton2 instance = null;
  private Singleton2() {}
  public static Singleton2 getInstance() {
    if (instance == null) {
      synchronized(Singleton2.class) {
        if (instance == null) {
          instance = new Singleton2();
        }
      }
    }
    return instance;
  }
}

This method of "optimizing" the synchronization block has been shown not to work.

Singletons are commonly found in C++ and J2SE applications. However their use in distributed J2EE environment becomes a lot more complex and subject to debate.

Synchronization

Lots of common enterprise requirements are implemented to some degree by the J2EE Container. One of these classic requirements is the need for multi-threading in enterprise applications. This is generally implemented by the use of a thread pool. Each client request coming in is assigned a worker thread to service the request. Since therefore any J2EE application by its nature is multithreaded, limiting the synchronization of threads is an important requirement.

"Synchronized" as shown is used on a Singletons getInstance method. This is to prevent the "==null" test which is not thread safe causing the creation of two Singleton objects, one of which will then end up being thrown away.

There is a way around the "synchronize" issue by using a "startup class" or a "startup Servlet". Since both these types of objects are initialized at container start up it is possible to use them to initialize the Singletons you need. It is then safe to drop the "synchronized" from the "getInstance" method.

The following StartUpServlet initializes several Singletons in its init method. This approach relies on the <load-on-startup> tag being placed in the Web Application's web.xml deployment descriptor.

import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.io.IOException;

public class StartUpServlet extends HttpServlet {
  public void init() throws ServletException {
    try {
      // Initialize the Singletons.
      // these would be defined in separate files
      EJBController.initInstance();
      CacheManager.initInstance();
      RMISingletonWrapper.initInstance();
      DAOFactory.initInstance();
      ServiceLocator.initInstance();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  public void doGet(HttpServletRequest request,
      HttpServletResponse response)
      throws ServletException, IOException {}
}

Clustering and RMI Singletons

Clustering is when you have J2EE containers that are running on different VMs talk to each other. Clustering is used to provide load balancing and fail over for J2EE clients.

The simple/local Singleton as shown is a non-distributed object. Therefore in a clustered environment you will end up with at least one Singleton object on each server. This of course may be ok for the design requirements.

However if the design is to have one Singleton for the cluster then a common approach is to implement a "pinned service". This refers to an RMI object that is only located on one container in the cluster. Its stub is then registered on the clustered JNDI tree making the object available cluster wide. This raises of causes one issue, what happens when the server containing the RMI Singleton crashes?

A Container in the cluster could try to bind a new RMI Singleton if it notices it is missing out of the JNDI tree. However this could cause issues if all the containers try to bind new RMI Singletons at the same time in response to a failure.

Generally at the end of the day RMI Singletons do tend to have the drawback that they end up as single points of failure.

In the following code example a local Singleton is used to act as a Wrapper around a RMI object that is bound into the clusters JNDI tree.

import javax.naming.*;
import java.rmi.*;

public class RMISingletonWrapper {
  private static RMISingletonWrapper instance = null;
  private static String SINGLETON_JNDI_NAME = "RMISingleton";

  public static RMISingletonWrapper getInstance() {
    return instance;
  }

  // All methods in delegate the method call to the actual
  // Singleton that lives on the clustered JNDI tree.
  public void delegate() {
    try {
      RMISingleton singleton = getRMISingleton();
      singleton.delegate();
    } catch (Exception e) {
      // Could try and recover
      e.printStackTrace();
    }
  }

  // Locate the true Singleton object in the cluster.
  private RMISingleton getRMISingleton() {
    RMISingleton rmiSingleton = null;
    try {
      Context jndiContext = new InitialContext();
      Object obj = jndiContext.lookup(SINGLETON_JNDI_NAME);
      rmiSingleton = (RMISingleton)PortableRemoteObject.narrow(
        obj,
        Class.forName("examples.singleton.rmi.RMISingleton"));
    } catch (Exception e) {
      // Could try and recover
      e.printStackTrace();
    }
    return rmiSingleton;
  }
}

Distributed Singleton Caches

One of the most common usages of Singletons is as caches of data. This use has issue for non RMI Singletons in a clustered environment. Problems happen when you attempt to do an update to the cache. Since a Singleton instance exists on each Container any update to the cached data by one Singleton will not be replicated to the other Singletons that exist on the other Containers.

This issue can be resolved by the use of the Java Messaging API to send update messages between Containers. In this approach if an update is made to the cache on one Container a message is published to a JMS Topic. Each Container has a listener that subscribes to that topic and updates its Singleton cache based on the messages it receives. This approach is still difficult as you have to make sure that the updates received on each container are handled in a synchronous fashion. JMS messages also take time to process so the caches may spend some time out of sync.

In the following simplistic implementation of a distributed Cache a CacheManager Singleton holds a Map of cached items. Items to be cached are placed in a CachItem object which implements the ICacheItem interface.

The CacheManager does not make any attempt to remove old items from the Cache based on any criteria like "Last Accessed Time".

import javax.jms.*;

public class CacheManager implements MessageListener {
  public static CacheManager instance = null;
  public static Map cache = new HashMap();

  private TopicConnectionFactory topicConnectionFactory;
  private TopicConnection topicConnection;
  private TopicSession topicSession;
  private Topic topic;
  private TopicSubscriber topicSubscriber;
  private TopicPublisher topicPublisher;

  private static final String CONNECTION_FACTORY_JNDI_NAME =
    "ConnectionFactory";
  private static final String TOPIC_NAME = "TopicName";

  public static void initInstance() {
    instance = new CacheManager();
  }

  public static CacheManager getInstance() {
    return instance;
  }

  public synchronized void addCacheItem(ICacheItem cacheItem) {
    CacheMessage cacheMessage = new CacheMessage();
    cache.put(cacheItem.getId(), cacheItem.getData());
    cacheMessage.setMessageType(CacheMessage.ADD);
    cacheMessage.setCacheItem(cacheItem);
    sendMessage(cacheMessage);
  }

  public synchronized void modifyCacheItem(ICacheItem cacheItem) {
    CacheMessage cacheMessage = new CacheMessage();
    cache.put(cacheItem.getId(), cacheItem.getData());
    cacheMessage.setMessageType(CacheMessage.MODIFY);
    cacheMessage.setCacheItem(cacheItem);
    sendMessage(cacheMessage);
  }

  public ICacheItem getCacheItem(String key) {
    return (ICacheItem)cache.get(key);
  }

  private CacheManager() {
    try {
      InitialContext context = new InitialContext();
      topicConnectionFactory = (TopicConnectionFactory)
        context.lookup(CONNECTION_FACTORY_JNDI_NAME);
      topicConnection = topicConnectionFactory.createTopicConnection();
      topicSession = topicConnection.createTopicSession(
        false, Session.AUTO_ACKNOWLEDGE);
      topic = (Topic) context.lookup(TOPIC_NAME);
      topicSubscriber = topicSession.createSubscriber(topic);
      topicSubscriber.setMessageListener(this);
      topicPublisher = topicSession.createPublisher(topic);
      topicConnection.start();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public void onMessage(Message message) {
    try {
      if (message instanceof ObjectMessage)  {
        ObjectMessage om = (ObjectMessage)message;
        CacheMessage cacheMessage = (CacheMessage)om.getObject();
        ICacheItem item =  cacheMessage.getCacheItem();
        interpretCacheMessage(cacheMessage);
      }
    } catch (JMSException jmse) {
      jmse.printStackTrace();
    }
  }

  private void interpretCacheMessage(CacheMessage cacheMessage) {
    ICacheItem cacheItem = cacheMessage.getCacheItem();
    if (cacheMessage.getMessageType()==CacheMessage.ADD) {
      synchronized (this) {
        cache.put(cacheItem.getId(), cacheItem.getData());
      }
    } else if (cacheMessage.getMessageType()==CacheMessage.MODIFY) {
      synchronized (this) {
        cache.put(cacheItem.getId(), cacheItem.getData());
      }
    }
  }

  private void sendMessage(CacheMessage cacheMessage) {
    try {
      Message message = topicSession.createObjectMessage(cacheMessage);
      topicPublisher.publish(message);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Class Loading

Containers tend to implement their own class loading structures to support hot deployment for J2EE components and class isolation WAR files.

Class isolation in WAR files means that all classes found in a WAR file must be isolated from other deployed WAR files. Each WAR file therefore is loaded by a separate instance of the Class loader. The purpose is to allow each WAR file have its own version of commonly named JSPs like "index.jsp".

If a Singleton class is located in several WAR files it will mean that a separate Singleton instance will be created for each WAR file. This may of course be ok for the required design but it is worth being aware of.

Common Implementations of a Singleton in J2EE

In conclusion I would like to talk about the three most common implementations of the Singleton pattern in J2EE I have come across. These are as Service Locators, Object Factories and Controllers.

Service Locators

A Service Locator allows you to cache objects (like EJB homes) that are located in the JNDI tree. JNDI look ups are very expensive so using a Singleton to cache these objects is a good idea. Since you never update these objects it does not matter that the cache is not distributed across a cluster.

In the following implementation of a ServiceLocator all lookups into the JNDI tree are cached into a Map. The ServiceLocator's getService method has a threading issue where it could place the JNDI Object into the map multiple times. This is due to the fact that the check to see if the object is already in the cache is not synchronized. The reason for dropping the synchronization is that it is more costly to do a "synchronization" than just ignoring an issue that if it happened would cause no real harm.

public class ServiceLocator {
  private static ServiceLocator instance = null;
  private Map cache = null;

  private ServiceLocator() throws NamingException {
    cache = Collections.synchronizedMap(new HashMap());
  }

  public static ServiceLocator getInstance()
      throws NamingException {
    return instance;
  }

  public static void initInstance() throws NamingException {
    instance = new ServiceLocator();
  }

  public Object getService(String entityName)
      throws NamingException {
    Object home = this.getFromCache(entityName);
    if (home == null) {
      try {
        Context jndiContext = new InitialContext();
        home = jndiContext.lookup(entityName);
      } catch (NamingException e) {
        e.printStackTrace();
      }
      this.putInCache(entityName, home);
    }
    return home;
  }

  private void putInCache(String key, Object value) {
    cache.put(key, value);
  }

  private Object getFromCache(String key) {
    return cache.get(key);
  }
}

Object Factory

An Object Factory class allows you to create an object of a certain type based on some form of an identifier. A common use of a factory in J2EE is in the creation of Data Access Objects. DAO's are commonly used to abstract SQL usage into specific classes. Clients obtain a factory Singleton and pass in the specific identifier for the specific DAO they need. The Object Factory instantiates the object and then returns a common interface to the client.

To allow possible support of multiple databases, a simple trick that can be used is to append a specific identifier to the DAO's class name. The implementer of the DAO class can then provide a specific implementation of the DAO for each database type if required. [HK: This is covered in our Design Patterns Course :-)]

It is possible that the factory could be implemented as a bunch of static methods. A reason not to take the static approach is the fact that maybe at some point the caching of DAO's will be required. If caching is required than the factories implementation can be changed to support caching without changing the factories' interface.

public class DAOFactory {
  private static DAOFactory instance = null;
  private String nameAppender = null;

  private final String ORACLE_DAO = "OracleDAO";
  private final String SQLServer_DAO = "SQLServerDAO";

  private DAOFactory() {
    // Maybe get this from a properties file
    nameAppender = ORACLE_DAO;
  }

  public static DAOFactory getInstance() {
    return instance;
  }

  public static void initInstance() {
    instance = new DAOFactory();
  }

  public IDAO getDAO(String daoName) throws DAOException {
    IDAO aDAO = null;
    try {
      aDAO = (IDAO)
        Class.forName(daoName + nameAppender).newInstance();
    } catch (ClassNotFoundException cnfex) {
      throw new DAOException(cnfex);
    } catch (IllegalAccessException ilaex) {
      throw new DAOException(ilaex);
    } catch (InstantiationException instex) {
      throw new DAOException(instex);
    } catch (Exception e) {
      throw new DAOException(e);
    }
    return aDAO;
  }
}

Controller

A Controller takes a request and based on some criteria directs that request to an object to be serviced. Since the container is running multiple threads and garbage collection is expensive it would be nice to have just one instance of the controller. A Singleton therefore is a good protocol independent Controller.

In the following implementation of an EJB Controller, the processRequest method takes a Request object that specifies the following; the Stateless EJBs Home interface class name, the Home interfaces JNDI name, the method name within the Stateless EJB to call and finally the list of parameters to pass in. The Response object contains the return object from the method call.

public class EJBController {
  private static EJBController instance = null;

  public static void initInstance() {
    instance = new EJBController();
  }

  public static EJBController getInstance() {
    return instance;
  }

  public Response processRequest(Request request) {
    Parameter ret = null;
    Object[] args = null;
    Class[] argTypes = null;
    Response response = new Response();

    List methodParameters = request.getMethodParameters();
    String methodName = request.getMethodName();

    if (methodParameters != null) {
      int parametersCount = methodParameters.size ();
      args = new Object[parametersCount];
      argTypes = new Class[parametersCount];
      for (int i = 0; i < parametersCount; i++) {
        Parameter param = (Parameter) methodParameters.get(i);
        args[i] = param.getValue ();
        argTypes[i] = param.getType ();
      }
    }
    try {
      EJBObject remoteObjRef = locateRemote(request);
      Class targetClass = remoteObjRef.getClass();
      Method m= targetClass.getMethod (methodName, argTypes);
      Object returnObject = m.invoke(remoteObjRef, args);
      response.setReturnObject(returnObject);
    } catch (InvocationTargetException e) {
      Throwable e1 = e.getTargetException();
      e1.printStackTrace();
    } catch (Throwable t) {
      t.printStackTrace();
    }
    return response;
  }

  // Locate the home interface of the JNDI tree and call its
  // create method
  private EJBObject locateRemote(Request request) {
    String homeInterfaceName = request.getHomeInterfaceName();
    String jndiName = request.getJNDIName();
    EJBObject remoteObjRef = null;
    try {
      Object obj = ServiceLocator.getInstance().getService(
        jndiName);
      EJBHome home = (EJBHome) PortableRemoteObject.narrow(
        obj, Class.forName(homeInterfaceName));
      Method createMethod = home.getClass().getMethod(
        "create", new Class[0]);
      remoteObjRef = (EJBObject) createMethod.invoke(
        (Object)home, new Object[0]);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return remoteObjRef;
  }
}

The Singletons presented here as common implementations of the pattern in J2EE can be thought of as either stateless or ones that at most control resources/objects that do not need to be cluster aware.


That was an excellent piece of writing, very clear, concise, to the point - Thank you very much, David.

Until the next newsletter...

Heinz

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...