|
The Java Specialists' Newsletter
Issue 052 2002-07-02
Category:
Software Engineering
Java version: J2EE Singletonby David Jones (Virgin Mobile USA)
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.
Thanks for reading this newsletter on our website. We also have a mailing list. That is where the real action takes place (webinars, free reports, etc.). Maybe subscribe today?
Advanced Java Courses on Crete:Java Specialists Master Course 18-21 June 2013 and
Concurrency Specialists Course 6-9 August 2013.
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 final static String CONNECTION_FACTORY_JNDI_NAME =
"ConnectionFactory";
private final static 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 ServiceLocators
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.
DAOs 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 DAOs
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 DAOs 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
Software Engineering Articles
Related Java Course
Discuss at The Java Specialist Club
|