In my implementation, the thread pool class,
test.ThreadPoolExecutor
is simply a subclass of java.util.concurrent.ThreadPoolExecutor
. It is implemented as a singleton so every lookup and injection always return the same instance.package test;The factory class,
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {
static final int defaultCorePoolSize = 5;
static final int defaultMaximumPoolSize = 10;
static final long defaultKeepAliveTime = 10;
static final TimeUnit defaultTimeUnit = TimeUnit.MINUTES;
static final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
private static ThreadPoolExecutor instance;
private ThreadPoolExecutor() {
super(defaultCorePoolSize, defaultMaximumPoolSize, defaultKeepAliveTime, defaultTimeUnit, workQueue);
}
synchronized static ThreadPoolExecutor getInstance() {
if (instance == null) {
instance = new ThreadPoolExecutor();
}
return instance;
}
}
test.ThreadPoolExecutorFactory
, is required to implement javax.naming.spi.ObjectFactory
. It also implements a GlassFish-specific interface com.sun.appserv.server.LifecycleListener
so that it can also be registered as a GlassFish lifecycle module. Upon receiving a server termination event, this class will shutdown the thread pool resource.package test;Copy 2 class files to $GLASSFISH_HOME/domains/domain1/lib/classes directory, with package name, and restart domain:
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.concurrent.TimeUnit;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import com.sun.appserv.server.LifecycleEvent;
import com.sun.appserv.server.ServerLifecycleException;
public class ThreadPoolExecutorFactory implements javax.naming.spi.ObjectFactory,
com.sun.appserv.server.LifecycleListener, java.io.Serializable {
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable<?, ?> environment) throws Exception {
ThreadPoolExecutor tp = ThreadPoolExecutor.getInstance();
try {
Reference reference = (Reference) obj;
Enumeration<?> enumeration = reference.getAll();
TimeUnit timeUnit = ThreadPoolExecutor.defaultTimeUnit;
long keepAliveTime = ThreadPoolExecutor.defaultKeepAliveTime;
while (enumeration.hasMoreElements()) {
RefAddr refAddr = (RefAddr) enumeration.nextElement();
String pname = refAddr.getType();
String pvalue = (String) refAddr.getContent();
if ("corePoolSize".equalsIgnoreCase(pname)) {
tp.setCorePoolSize(Integer.parseInt(pvalue));
} else if ("maximumPoolSize".equalsIgnoreCase(pname)) {
tp.setMaximumPoolSize(Integer.parseInt(pvalue));
} else if ("timeUnit".equalsIgnoreCase(pname)) {
timeUnit = TimeUnit.valueOf(pvalue);
} else if ("keepAliveTime".equalsIgnoreCase(pname)) {
keepAliveTime = Long.parseLong(pvalue);
} else if ("allowCoreThreadTimeOut".equalsIgnoreCase(pname)) {
tp.allowCoreThreadTimeOut(Boolean.parseBoolean(pvalue));
} else if ("prestartAllCoreThreads".equalsIgnoreCase(pname)) {
if (Boolean.parseBoolean(pvalue)) {
tp.prestartAllCoreThreads();
}
} else {
throw new IllegalArgumentException("Unrecognized property name: " + pname);
}
}
tp.setKeepAliveTime(keepAliveTime, timeUnit);
} catch (Exception e) {
throw (NamingException) (new NamingException()).initCause(e);
}
return tp;
}
public void handleEvent(LifecycleEvent event) throws ServerLifecycleException {
if (event.getEventType() == LifecycleEvent.TERMINATION_EVENT) {
ThreadPoolExecutor tp = ThreadPoolExecutor.getInstance();
System.out.println("About to purge and shutdown " + tp + ", active thread count: "
+ tp.getActiveCount());
tp.purge();
tp.shutdown();
}
}
}
ls $GLASSFISH_HOME/domains/domain1/lib/classes/test/Next, create the thread pool resource, and register lifecycle module. It can also be done in admin console, in a more user-friendly manner.
ThreadPoolExecutorFactory.class ThreadPoolExecutor.class
$ asadmin restart-domain
$ asadmin create-custom-resource --restype test.ThreadPoolExecutor --factoryclass test.ThreadPoolExecutorFactory --description "A ThreadPoolExecutor backed by LinkedBlockingQueue" --property corePoolSize=6:maximumPoolSize=50:keepAliveTime=4:timeUnit=MINUTES:allowCoreThreadTimeOut=true:prestartAllCoreThreads=true concurrency/TPTo list and delete custom resources and lifecycle modules:
Command create-custom-resource executed successfully.
$ asadmin create-lifecycle-module --classname "test.ThreadPoolExecutorFactory" --failurefatal=true concurrency/TP-shutdown
$ asadmin list-custom-resourcesThis is how it looks like in admin console, where you can easily manage it:
$ asadmin list-lifecycle-modules
$ asadmin delete-lifecycle-module concurrency/TP-shutdown
$ asadmin delete-custom-resource concurrency/TP
To use this resource, just inject or look up in application components. The following is a test servlet that verifies that the resource can be obtained with either @Resource or regular lookup, and that multiple lookups return the same instance.
package test;When running the test webapp at http://localhost:8080/test/, many log messages like these will appear in server.log:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.annotation.*;
import javax.naming.*;
@javax.servlet.annotation.WebServlet(urlPatterns = "/*")
public class TestServlet extends HttpServlet {
@Resource(name="java:app/env/concurrency/TP", mappedName="concurrency/TP")
private test.ThreadPoolExecutor tp;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
testLookup();
System.out.println("About to submit tasks to " + tp);
for(int i = 0; i < 30; i++) {
tp.execute(new MyRunnable());
}
}
private void testLookup() throws ServletException {
try {
for(int i = 0; i < 10; i++) {
ThreadPoolExecutor t = InitialContext.<ThreadPoolExecutor>doLookup("java:app/env/concurrency/TP");
System.out.println("ThreadPoolExecutor from lookup: " + t);
}
} catch (NamingException e) {
throw new ServletException(e);
}
}
private static class MyRunnable implements Runnable {
public void run() {
System.out.println("Executing task in " + Thread.currentThread());
}
}
}
ThreadPoolExecutor from lookup: test.ThreadPoolExecutor@12e0a75aIn the above output,
Executing task in Thread[pool-29-thread-6,5,grizzly-kernel
pool-29-thread-6
is the thread name, 5 (normal) is its priority, and grizzly-kernel
is thread group name. When shutting down the server, the thread pool is purged and shutdown, thanks to the lifecycle module concurrency/TP-shutdown
:Server shutdown initiatedThis is just a simplistic implementation of thead pool resource in GlassFish. There is a potential class loader leak and may cause ClassCastException and/or OutOfMemoryError in large applications.
About to purge and shutdown test.ThreadPoolExecutor@12e0a75a, active thread count: 0
JMXStartupService: Stopped JMXConnectorServer: null
JMXStartupService and JMXConnectors have been shut down.
Shutdown procedure finished