|
JavaFAQ Home » Story by Dr. Kabutz

The Java Specialists' Newsletter [Issue 052] - J2EE Singleton
Author: David Jones (Virgin Mobile USA)
JDK version:
Category: Software Engineering
You can subscribe from our home page:
http://www.javaspecialists.co.za (which also hosts all previous issues,
available free of charge
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.
Next week I am presenting a Java course in Cape Town, there is only one place
left (someone has asked, but not confirmed, and as you know, first-come,
first-served). If you live in the Cape Town area, and you would like to attend
this course, please have a look at
our
website.
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 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 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
Copyright 2000-2004 Maximum Solutions, South Africa
Reprint Rights. Copyright subsists in all the material included
in this email, but you may freely share the entire email with anyone you feel
may be interested, and you may reprint excerpts both online and offline provided
that you acknowledge the source as follows: This material from The Java(tm)
Specialists' Newsletter by Maximum Solutions (South Africa). Please contact
Maximum Solutions for more
information.
Java and Sun are trademarks or registered trademarks of Sun Microsystems,
Inc. in the United States and other countries. Maximum Solutions is independent
of Sun Microsystems, Inc. Printer Friendly Page
Send to a Friend
..
Search here again if you need more info!
|