Summary: Asynchronous beans provide an efficient and safe global thread pool that can be used by multiple applications. When you require the use of specialized thread pools, you can construct a thread factory using the Asynchronous Beans EventSource interface with IBM® WebSphere® Application Server V5.x or V6.x, and achieve the freedom of using whatever advanced thread usage patterns are necessary without sacrificing performance.
Introduction
IBM WebSphere Application Server software provides two mechanisms that enable J2EE™ application developers to use threads safely in servlets and EJB components:
- Asynchronous beans
- Commonj Timer and WorkManager for Application Servers 1.1 specification.
Both programming models let you create pooled and daemon threads to run J2EE business logic.
In both programming models, threads can be reused by different applications. This is achieved by applying and removing the J2EE context information on and off the thread when the application logic begins and ends. A single thread pool can therefore be used by multiple applications. The identity of the thread will change each time it is used.
The context of the thread must be changed each time the thread is reused, which can cause a significant overhead to applications that may be doing very little activity on those threads. In these cases, it is desirable to have a component-scoped thread pool, with a fixed J2EE context on each thread. This can be accomplished using the Asynchronous Beans EventSource interface.
This article describes how a thread factory can be constructed using an Asynchronous Beans EventSource, and includes a downloadable sample, called the Concurrent Adapter, which can be used with third-party thread pool implementations to create fast thread pools that will work on WebSphere Application Servers.
Global thread pooling
WebSphere Application Server ships a high-performance and highly-scalable thread pool implementation. The WorkManagers from asynchronous beans and Commonj both use this thread pool for all pooled threads.
Since the WorkManager instances are available in the global namespace, they can be shared amongst multiple applications and, therefore, require J2EE context switching. To achieve this, the WorkManager takes a snapshot of the J2EE context on the thread when the work is submitted. The resulting object becomes a WorkWithExecutionContext (WWEC) object (Figure 1).
Figure 1. WorkWithExecutionContext When using the WorkManager as a global thread pool (Figure 2), each work submitted to the thread pool will have the application's context applied and removed from the thread each time the work is allocated to a thread:
Work is submitted to the WorkManager thread pool (the blue box)
- A snapshot is taken of the J2EE application context and stored with the work as a WWEC object.
- The WWEC is added to the pool's input queue.
A worker thread pulls the next WWEC from the input queue and runs it.
- A snapshot is taken of the current J2EE application context on the worker thread to restore later after the work completes.
- The J2EE context stored with the WWEC is applied to the thread.
- The work is run.
- The J2EE context is removed from the thread and the previous context is reapplied.
The worker thread now waits for more work to appear on the input queue.
Figure 2. Global thread pool sharing with a WorkManager
Component-scoped thread pooling
If a thread pool is only going to be used by a single application or component (a servlet or EJB) and the work to be submitted can tolerate a single, common J2EE context identity, then using a custom thread pool with a thread factory can significantly increase performance. This is a component-scoped thread pool.
Component-scoped thread pool threads will all share the J2EE context of the application component that created it. If created, for example, by a startup bean, each thread will contain the context of the startup bean's start() method, and each thread will behave as if it were running within the scope of that startup bean. The startup bean is therefore the owner of the thread pool instance. All business logic will run with the java:comp namespace and security context of the startup bean's start() method, regardless of the servlet or EJB that submits the work.
All threads in a component-scoped thread pool are asynchronous bean daemon threads and have the same lifecycle of the application that created it. If the application ends, the release() method of each daemon Work thread in the pool will be called.
When using custom component-scoped thread pools (Figure 3), each worker thread in the pool is initialized with a daemon thread created by the WorkManager. The WorkManager becomes a thread factory. Each thread will share the same J2EE context of the pool creator:
A Runnable is submitted to a custom thread pool.
A thread pool worker thread pulls the next WWEC from the input queue and runs it. Each worker thread has the J2EE context of the thread pool creator component applied to it.
- The Runnable is run on the J2EE worker thread.
- When complete, the J2EE worker thread remains active.
The J2EE worker thread now waits for more work to appear on the input queue.
Figure 3. Component-scoped thread pool
Custom thread pools
The Asynchronous Beans WorkManager does not externalize the thread pool, so there is no way to change the default behavior. To implement a component-scoped thread pool, a third-party thread pool must be used in conjunction with the J2EE context switching capability of asynchronous beans.
Several thread pool implementations exist that will work fine with this model. One of the more accepted implementations is Doug Lea's EDU.oswego.cs.dl.util.concurrent.PooledExecutor which will run on J2SE 1.2 and later. This thread pool has evolved into J2SE 5's java.util.concurrent.ThreadPoolExecutor, which has also been back-ported to J2SE 1.4.
Both the PooledExecutor and ThreadPoolExecutor implementations are in the public domain and are downloadable (see Resources).
Below are suggested thread pool implementations, by WebSphere Application Server version:
WebSphere Application Server Enterprise V5.0, J2SE 1.3:
- EDU.oswego.cs.dl.util.concurrent.PooledExecutor
WebSphere Busness Integration Server Foundation V5.1 and
WebSphere Application Server (all editions) V6.0, J2SE 1.4:
- edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor
- EDU.oswego.cs.dl.util.concurrent.PooledExecutor
WebSphere Application Server (all editions) V6.1, J2SE 5:
- edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor
- EDU.oswego.cs.dl.util.concurrent.PooledExecutor
- java.util.concurrent.ThreadPoolExecutor
These implementations are very similar and utilize a thread factory, which enables plugging-in a customized J2EE-aware thread factory. In this case, the custom thread factory is a wrapper around an Asynchronous Beans EventSource. (See the Concurrent Adapter sample.) These implementations behave similarly to the WebSphere thread pool implementation. If using J2SE 1.4, the back port of java.util.concurrent is recommended, as it has more functionality and will simplify future migration to J2SE 5. If using J2SE 5, using java.util.concurrent directly is suggested.
Asynchronous Beans EventSource
An EventSource is a mechanism of the Asynchronous Beans WorkManager that enables dynamic application of J2EE context from a thread onto any other thread. It provides a way to safely communicate between applications or security contexts in the same process.
For example, if a servlet wants to be notified when a stock price has changed, it can register a listener on a well-known EventSource. When the stock price daemon publishes the change, using a system account, each listener will be notified, but in the listener's security context (Figure 4).
Figure 4. Security context switching with an EventSource The same technique can be applied to any POJO (plain old Java™ object), which can be wrapped with an EventSource to enable switching the J2EE context of the object for a single method call. EventSources use the java.lang.reflect.Proxy object to wrap each object instance with a specialized, J2EE java.lang.reflect.InvocationHandler. When the proxy is used to invoke the Java object method, the J2EE handler will automatically apply the J2EE context on the thread that was captured when the listener was registered.
This feature is particularly useful when methods are being executed on out-of-scope threads; for example, starting a thread on a thread pool.
Without an EventSource proxy, a new thread will have no J2EE context. The thread pool will instantiate a new java.util.Thread object and call the start() method.
The EventSource enables us to implement a ThreadFactory implementation that can be plugged into a PooledExecutor or ThreadPoolExecutor. The J2EE context of the J2EE component that creates the ThreadFactory will be preserved and reapplied to the thread prior to starting a new worker thread. All the overhead of switching contexts is applied to the thread one time.
Since the EventSource object does not exist in the Commonj WorkManager specification, it is not possible to build a thread factory. A thread factory is only available when using asynchronous beans.
Implementing a thread factory
With this article, we include sample thread factory implementations that enable the use of custom, component-scoped thread pools from J2SE 5, J2SE 1.4 (using the back port of J2SE 5 java.util.concurrent.ThreadPoolExecutor), or with the dl.util.concurrent.PooledExecutor in a WebSphere application. (See the Javadoc for the com.ibm.websphere.sample.concurrentadapter package, included in the download file for details on prerequisites and how to build the WASThreadFactory.)
WASThreadFactory
The WASThreadFactory is the implementation for the ThreadFactory. It uses a WASThreadFactoryBase base class to enable the use of a single implementation for both the J2SE 5 and the dl.concurrent.util version of the ThreadFactory (Figure 5).
Figure 5. WASThreadFactory class diagram The WASThreadFactory takes a WorkManager as a parameter on the constructor, and uses it to create an EventSource and register a listener proxy for the WorkManager. When a thread is requested from the WASThreadFactory, the thread will be created using the WorkManager, but will contain the J2EE context of the component that created the WASThreadFactory instance. This is known as a single-context thread pool.
Single-context and multi-context thread pools
When using a custom thread pool such as a ThreadPoolExecutor, it may not be desirable to run work within a single J2EE context. It may be required, for example, to propagate the submitter's security context.
In this case, a J2EE context-aware Executor (ContextExecutor) can be used to attach the current J2EE context to the Runnable prior to execution. Each worker thread in the pool will be primed with the J2EE context of the WASThreadFactory creator, but when using the ContextExecutor, the Runnable will first apply the J2EE context of the submitter to the thread and then remove it when finished.
The same ThreadPoolExecutor is used to directly submit work to the pool that can utilize the default J2EE context. The ContextExecutor wrapper is used to submit work to the ThreadPoolExecutor, but with the submitter's context.
Figure 6. Custom thread pool with default and multi- contexts Usage example: WASThreadFactory
To use the WASThreadFactory, the application must first look up an Asynchronous Beans WorkManager, create a WASThreadFactory instance, and then construct a new thread pool. Working examples using a servlet are available in the sample download file.
// Lookup the WorkManager and construct the WASThreadFactory InitialContext ctx = new InitialContext(); WorkManager wm = (WorkManager)ctx.lookup("java:comp/env/wm/default"); ThreadFactory tf = new WASThreadFactory(wm); // Create a ThreadPoolExecutor using a bounded buffer BlockingQueue q = new ArrayBlockingQueue(10); ThreadPoolExecutor pool = new ThreadPoolExecutor( 1, 10, 5000, TimeUnit.MILLISECONDS, q, tf); // Use the submit or execute methods to submit work to the pool pool.submit(myRunnable); |
Usage example: ContextExecutor
Using a WASThreadFactory with a ContextExecutor to enable submitting Runnables to a custom thread pool with the submitter's J2EE context is as simple as creating a ContextExecutor instance. A ContextExecutorService can also be used to track the state of the submitted task.
// Create a ThreadPoolExecutor ThreadPoolExecutor pool = new ThreadPoolExecutor( 1, 10, 5000, TimeUnit.MILLISECONDS, q, tf); // Wrap the ThreadPoolExecutor with a ContextExecutor ContextExecutor cePool = new ContextExecutor(wm, pool); // Use the ContextExecutor.execute() method to submit work to the pool. cePool.execute(myRunnable) |
Installing and running the samples
This article includes three sample applications; one for each of the thread pool implementations described. Each application consists of a single EAR that can be installed on a WebSphere Application Server. Each EAR contains:
- a Web module (WAR)
- the ABConcurrencyUtils.jar utility JAR file that contains the code common to all of the samples (including the WASThreadFactory)
- a utility JAR that includes the specific code for the thread pool implementation.
Each WAR contains three servlets:
FactoryTestServlet: A simple example that shows how to create the WASThreadFactory and submit a number of Runnable tasks to it.
ABBenchmarkServlet: An example that shows the number of micro-seconds it takes to run an asynchronous bean work object without using the WASThreadFactory.
FactoryBenchmarkServlet: An example that shows the number of micro-seconds it takes to run a Runnable using the WASThreadFactory.
Each module includes the source and binaries and can be directly imported into IBM Rational® Application Developer V6. (At the time of this writing, Rational Application Developer does not currently support JDK 5. The JDK5 utility JAR in the JDK5 sample will not compile in Rational Application Developer and must be built separately.)
Prerequisites
Each sample requires asynchronous beans and the WorkManager with JNDI name wm/default; this WorkManager is created by default during installation. The samples have only been tested on the single-server version of WebSphere Application Server. Although these samples have not been tested using WebSphere Application Server Network Deployment, or the unit test environments of Rational Application Developer or WebSphere Studio Application Developer Integration Edition, we expect the samples to operate as expected in these environments.
Sample 1: ABConcurrencyTester_JDK5.ear
This sample can be installed and run on WebSphere Application Server V6.1 and later. It utilizes java.util.concurrent.ThreadPoolExecutor, which is included with J2SE 5.
To run the sample:
- Start WebSphere Application Server.
- Install ABConcurrencyTester_JDK5.ear using either wsadmin scripting or the administrative console. Use the default options.
- Start the ABConcurrencyTester_JDK5 application.
- Run the samples using the following URL (where is the IP address or name of your application server host machine, and is the HTTP listener port for the application server):
http://:/ABConcurrencyTester_JDK5
Sample 2: ABConcurrencyTester_JDK14.ear
This sample can be installed and run on IBM WebSphere Business Integration Server Foundation V5.1 and WebSphere Application Server (all editions) V6.0 and later. It utilizes the java.util.concurrent back port to JDK 1.4.
To run the sample:
- Download backport-util-concurrent.jar from http://dcl.mathcs.emory.edu/util/backport-util-concurrent/.
- Add backport-util-concurrent.jar to the root of the ABConcurrencyTester_JDK14.ear.
- Install the ABConcurrencyTester_JDK14.ear using either wsadmin scripting or the administrative console. Use the default options.
- Start the ABConcurrencyTester_JDK14 application.
- Run the samples using the following URL (where is the IP address or name of your application server host machine, and is the HTTP listener port for the application server):
http://:/ABConcurrencyTester_JDK14
Sample 3: ABConcurrencyTester_DL.ear
This sample can be installed on WebSphere Application Server Enterprise V5.0.2, WebSphere Business Integration Server Foundation V5.1, and WebSphere Application Server (all editions) V6.0 and later. It utilizes the dl.util.concurrent package, which runs on JDK 1.2 and later.
To run the sample:
- Download and build concurrent.jar using a JDK 1.3 compiler (if deploying to WebSphere Application Server Enterprise V5.0 or later) or a JDK 1.4 compiler (if deploying to WebSphere Business Integration Server Foundation V5.1 or later). The utility comes with an ANT script that will create concurrent.jar. (You can use ws_ant.bat or ws_ant.sh scripts in the application server's bin directory to build the concurrent.jar.) Download the utility from http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html.
- Rename concurrent.jar to dl-util-concurrent.jar.
- Add dl-util-concurrent.jar to the root of the ABConcurrencyTester_DL.ear.
- Install the ABConcurrencyTester_DL.ear using either wsadmin scripting or the administrative console. Use the default options.
- Start the ABConcurrencyTester_DL application.
- Run the samples using the following URL (where is the IP address or name of your application server host machine and is the HTTP listener port for the application server):
http://:/ABConcurrencyTester_DL
Conclusion
Asynchronous beans provides an efficient and safe global thread pool that can be used by multiple applications. In the event that specialized thread pools are required, the Asynchronous Beans EventSource can be used to create a ThreadFactory. The J2EE-aware ThreadFactory can be used to create threads primed with a set J2EE context.
The WASThreadFactory, used in the Concurrent Adapter sample in this article, enables J2EE application developers the freedom to utilize any advanced thread usage patterns without sacrificing performance.