Be careful with Singleton Session Bean

Before we start to talk about the new Singleton Session Bean introduced in Java EE 6, let’s refresh our memory on Stateless Session Bean. Stateless Session Bean was designed to achieve the scalability with a easy Single Threaded Programming model. When a new request comes in, EJB container will either picks up a Bean from the pool or create a new one, bind this bean to the request/thread through setSessionContext(), and guarantee this instance will not be reused by another request/thread until this Bean instance is returned back to the pool. As a general guideline of develpoing Stateless Session Bean, you normally would not have any other instance level properties except the SessionContext. Thus any one of the Bean instance could be tied to any one of the requests/threads and we consider them as Stateless. You can roll back transaction or figure out the caller identity through the SessionContext injected by the Container.
Then in Java world, Singleton design pattern is well known and widely used to code the Business and DAO layer where ONE instance of Java Object is created and used to serve all the threads cocurrently throughout the whole JVM. If you are using Spring Framework, you are probably coding most of if not all of your Beans as Singleton instead of Prototype bean. Performance is better here because there is no need for Garbage Collection at all. However you have to be very careful and very good at multi-thread programming if you start to put properties into this Bean because all threads could read/write them.
Now we are pretty clear Stateless Session Bean is NOT Singleton Bean. They are pool of them to serve the incoming requests although it is very effcient. In one of my tests in old days with WebSphere 5 on a PC, Only 10 instances were created to serve 1000 cocurrent requests.
So the Singleton Session Bean introduced in Java EE 6 is a Bean follows the Singleton desgin pattern. There is only one instance through the whole JVM to serve all the threads. You have to be very careful when you are using it because:

  • If no @Lock annotation is present on the singleton class, the default lock type of @Lock(WRITE) is applied to all business and timeout methods. Then according to the spec, the singleton session bean should be locked to other clients while a client is calling a method annotated with @Lock(WRITE). That means if you forgot to annotate your calss as @Lock(READ), you are creating a huge bottleneck that probably really go against your original purpose because multiple lanes free way traffic suddenly need to squish through an one lane local road. As demoed in my sample codes, I’d rather annotate the whole with READ access because most of time we want multi-threads read behavior, then annotate one or a few methods with WRITE prilige to block all other threads when it needs to update the shared properties. This would be the purpose of having Singleton Bean, if you need to annote WRITE in a few places, you need spend more time to rethink your design again.
  • You cannot just convert your Stateless Session Bean into Singleton Bean even you don’t have any shared property there. That’s quite a surprise, right? But remember in the very begining I said there is a SessionContext ties to a Bean instance when it is placed by the Container to serve the request. This SessionContext is often used to roll back transactions and figure out the user identity and roles. However based on my test codes in GlassFish V3, there is only ONE SessionContext assigned to the same one Singleton Session Bean to serve all the threads. That means you should never use this context anymore because it simply is NOT a execution context at all. Your system would have unpredictable behavior when you use the SessionContext in the way you are used to in Stateless Session Bean.
  • Although Singleton session beans maintain their state between client invocations, but are not required to maintain their state across server crashes or shutdowns. My guess is that not all server implementations will replicate the changes to the shared properties in the Singleton Session Bean to the instances of other servers if you have a cluster environment. This might or might not be a problem depends on your implementation. But at least you have to make sure your design would not defect the purpose of cluster environment with the introduction of this Singleton Session Bean.

I wrote a sample project to prove my first and second points.

@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@Lock(LockType.READ)
public class SingletonSessionTestBean {
	@Resource SessionContext context;

	private StringBuilder builder;

	@Lock(LockType.WRITE)
	public void clear() {
		builder= new StringBuilder();
	}

	@Lock(LockType.WRITE)
	public void writeTest() {
		long writeTime = System.currentTimeMillis();
		builder.append("Thread " + Thread.currentThread().getId() +
                                         ", From writeTest at time " +
                                         writeTime + ". SessionContext HashCode: " +
                                         context.hashCode() + "\n");
		writeTest1();
		writeTest2();
	}

	protected void writeTest1() {
		builder.append("Thread " + Thread.currentThread().getId() +
                                         ", From writeTest1.\n");
		//Try to yield the right of CPU to other threads
		Thread.yield();
	}

	protected void writeTest2() {
		builder.append("Thread " + Thread.currentThread().getId() +
                                         ", From writeTest2.\n");
	}

	public void readTest() {
		long readTime = System.currentTimeMillis();
		synchronized(builder) {
			builder.append("Thread " + Thread.currentThread().getId() +
                                                 ", From readTest at time " + readTime + ".\n");
		}
	}

	public String getResult() {
		return builder.toString();
	}
}

In order to prove that only one thread can acquire the WRITE lock and operate on the whole object, I have one writeTest() method annoated with @Lock(LockType.WRITE). This method calls writeTest1() and writeTest2(). In writeTest1() method, I tried to call Thread.yield() to yield the right of CPU to other threads. If the whole object is not blocked by the WRITE lock, one of the other threads should have the chance to generate some read or write texts in between the writeTest1 and writeTest2. As we can see from the snapshot, writeTest2 from the same thread always follows the writeTest1. That means the whole object is locked when the WRITE method is hit.

If we look at the snapshot carefully, we can see that a few threads generate the readTest message at the same time. That proves multiple threads are able to acquire the READ access at the same time.
To prove all threas use the same SessionContext actually is the easiest thing. You can see that all threads generate the same hash code for its SessionContext object.

The client codes that generate a few threads and call the SingletonSessionTestBean are:

public class SingletonClient extends Thread {
	private SingletonSessionTestBean singletonSessionTestBean;
	private CountDownLatch doneSignal;

	public SingletonClient(SingletonSessionTestBean singletonSessionTestBean, CountDownLatch doneSignal) {
		this.singletonSessionTestBean = singletonSessionTestBean;
		this.doneSignal = doneSignal;
	}

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			singletonSessionTestBean.writeTest();
			singletonSessionTestBean.readTest();
		}
		doneSignal.countDown();
	}
}

@ManagedBean
public class SingletonTestBean extends BaseBean {
	@EJB private SingletonSessionTestBean singletonSessionTestBean;

	private String result = "";

	public void test() throws Exception {
		singletonSessionTestBean.clear();

		int count = 5;

		CountDownLatch doneSignal = new CountDownLatch(count);
		SingletonClient[] clientArray = new SingletonClient[count];

		//Generate threads and call SingletonSessionTestBean
		for (int i = 0; i < count; i++) {
			clientArray[i] = new SingletonClient(singletonSessionTestBean, doneSignal);
		}

		for (SingletonClient client : clientArray) {
			client.start();
		}

		//Wait for both threads finish
		doneSignal.await();

		result = singletonSessionTestBean.getResult();
	}

	public String getResult() {
		return result;
	}
}

SingletonClient extends the Thread class is the place where the writeTest() and readTest() methods of SingletonSessionTestBean get called 100 times. SingletonTestBean is a JSF managed bean that creates 5 threads (SingletonClient). Another interesting piece is I used CountDownLatch to wait for all 5 threads finish their running.

So the conclusion is you have to fully understand the impacts of Singleton Session Bean and be very careful when you are using it. It would be helpful to the developers who are not very familiar with Java concurrency package introduced in JDK 5 or not comfortable with the old “synchronized” way to writing multi-thread codes.

You can download the source codes of this demo project at here

Leave a Reply

You must be logged in to post a comment.