How to create and look up thread pool resource in GlassFish

In order to create a custom thread pool task executor resource, one needs to implement 2 classes: the thread pool class, and its object factory class.

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;

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;
}
}
The factory class, 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;

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();
}
}
}
Copy 2 class files to $GLASSFISH_HOME/domains/domain1/lib/classes directory, with package name, and restart domain:
ls $GLASSFISH_HOME/domains/domain1/lib/classes/test/
ThreadPoolExecutorFactory.class ThreadPoolExecutor.class

$ asadmin restart-domain
Next, create the thread pool resource, and register lifecycle module. It can also be done in admin console, in a more user-friendly manner.
$ 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/TP
Command create-custom-resource executed successfully.

$ asadmin create-lifecycle-module --classname "test.ThreadPoolExecutorFactory" --failurefatal=true concurrency/TP-shutdown
To list and delete custom resources and lifecycle modules:
$ asadmin list-custom-resources

$ asadmin list-lifecycle-modules

$ asadmin delete-lifecycle-module concurrency/TP-shutdown

$ asadmin delete-custom-resource concurrency/TP
This is how it looks like in admin console, where you can easily manage it:

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;
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());
}
}
}
When running the test webapp at http://localhost:8080/test/, many log messages like these will appear in server.log:
ThreadPoolExecutor from lookup: test.ThreadPoolExecutor@12e0a75a
Executing task in Thread[pool-29-thread-6,5,grizzly-kernel
In the above output, 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 initiated
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
This 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.

Followers

Pageviews Last 7 Days