Home | FAQ | Contact me

Per-thread local store in Java

Per-thread data can be useful. Here's how to do it in the context of servlet code because servlet code requires a more precise approach.

Inside this code

In terms of beginning Java samples you have:

ThreadContext.java

Here's the per-thread data we want to keep for use in issuing richer statements to the log.

package com.acm.web.user.thread;

import java.util.Date;

/**
 * Thread-safe collection of state such that each thread will get its own instance
 * of everything below, almost only for the purpose of logging, without any of this
 * being trampled by other threads executing the same classes and code paths.
 *
 * Clean-up
 *
 * This data must be formally abandoned via the remove() method below at the end
 * of the thread's work because the executing thread comes from a pool maintained
 * by Tomcat.
 */
public class ThreadContext
{
    // general status...
    private String   ipaddress;
    private boolean  ping;              // our app is being ping'd

    // request/executing status...
    private String   httpmethod;
    private String   httpuri;
    private String   httpbaseuri;
    private String   httpqueryparms;
    private Date     requesttime;

    // response status...
    private Date     responsetime;
    private int      httpstatus;

    public String getIpaddress() { return this.ipaddress; }
    public void setIpaddress( String ipaddress ) { this.ipaddress = ipaddress; }
    public boolean isPing() { return this.ping; }
    public void setPing( boolean ping ) { this.ping = ping; }
    public String getHttpmethod() { return this.httpmethod; }
    public void setHttpmethod( String httpmethod ) { this.httpmethod = httpmethod; }
    public String getHttpuri() { return this.httpuri; }
    public void setHttpuri( String httpuri ) { this.httpuri = httpuri; }
    public String getHttpbaseuri() { return this.httpbaseuri; }
    public void setHttpbaseuri( String httpbaseuri ) { this.httpbaseuri = httpbaseuri; }
    public String getHttpqueryparms() { return this.httpqueryparms; }
    public void setHttpqueryparms( String httpqueryparms ) { this.httpqueryparms = httpqueryparms; }
    public Date getRequesttime() { return requesttime; }
    public void setRequesttime( Date requesttime ) { this.requesttime = requesttime; }
    public Date getResponsetime() { return responsetime; }
    public void setResponsetime( Date responsetime ) { this.responsetime = responsetime; }
    public int getHttpstatus() { return httpstatus; }
    public void setHttpstatus( int httpstatus ) { this.httpstatus = httpstatus; }


    // -----------------------------------------------------------------------------------------------
    // Above this line is our per-thread data that we want accessible at any random point in our code.
    // Below are the mechanics of implementing per-thread data in Java.
    // -----------------------------------------------------------------------------------------------

    private static final ThreadLocal< ThreadContext > context = new ThreadLocal< ThreadContext >()
    {
        @Override
        protected ThreadContext initialValue()
        {
            ThreadContext context = new ThreadContext();

            context.ping = false;
            context.requesttime = new Date();
            return context;
        }
    };

    public static ThreadContext get()    { return context.get(); }
    public static void          remove() { context.remove(); }

    /**
     * This is more for use by the Eclipse debugger than anything else.
     */
    public String toString()
    {
        StringBuffer sb = new StringBuffer();

        sb.append( "{\n" );

        if( this.ipaddress      != null ) sb.append( "  ipaddress:      " + this.ipaddress               + "\n" );
                                   sb.append( "  ping:           " + this.ping                    + "\n" );
        if( this.httpmethod     != null ) sb.append( "  httpmethod:     " + this.httpmethod              + "\n" );
        if( this.httpuri        != null ) sb.append( "  httpuri:        " + this.httpuri                 + "\n" );
        if( this.httpbaseuri    != null ) sb.append( "  httpbaseuri:    " + this.httpbaseuri             + "\n" );
        if( this.httpqueryparms != null ) sb.append( "  httpqueryparms: " + this.httpqueryparms          + "\n" );
        if( this.requesttime    != null ) sb.append( "  requesttime:    " + this.requesttime.toString()  + "\n" );
        if( this.responsetime   != null ) sb.append( "  responsetime:   " + this.responsetime.toString() + "\n" );
                                   sb.append( "  httpstatus:     " + this.httpstatus              + "\n" );
        sb.append( "}" );

        return sb.toString();
    }
}

In the code below, especially for the tear-down code, I think the comments say it all.

ContextCreationFilter.java
package com.acme.web.filter;

import java.net.URI;

import org.apache.log4j.Logger;

import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import com.sun.jersey.spi.container.ResourceFilter;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Context;

import com.acme.web.user.thread.ThreadContext;

public class ContextCreationFilter implements ResourceFilter, ContainerRequestFilter
{
    private static Logger log = Logger.getLogger( ContextCreationFilter.class );

    @Context HttpServletRequest httpRequest;

    public ContextCreationFilter()
    {
        log.info( "ContextCreationFilter is installed" );
    }

    /**
     * Set up per-thread context for gathering state that will be used to issue
     * statements to the log.
     */
    @Override
    public ContainerRequest filter( ContainerRequest request )
    {
        ThreadContext ctx = ThreadContext.get();        // get our thread-local store...
        URI           uri = request.getRequestUri();

        ctx.setIpaddress( httpRequest.getRemoteAddr() );
        ctx.setHttpmethod( request.getMethod() );
        ctx.setHttpuri( request.getRequestUri().toASCIIString() );
        ctx.setHttpbaseuri( request.getBaseUri().getPath() );
        ctx.setHttpqueryparms( uri.getQuery() );

        String ping = request.getPath();

        if( StringUtil.isEmptyOrNullLiteral( ping ) )
            ctx.setPing( true );

        return request;
    }

    @Override
    public ContainerRequestFilter getRequestFilter()
    {
        return this;
    }

    @Override
    public ContainerResponseFilter getResponseFilter()
    {
        return null;
    }
}
ContextTearDownFilter.java
package com.acme.web.filter;

import org.apache.log4j.Logger;

import com.acme.web.user.thread.ThreadContext;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import com.sun.jersey.spi.container.ResourceFilter;

public class ContextTearDownFilter implements ResourceFilter, ContainerResponseFilter
{
    private static Logger log = Logger.getLogger( ContextTearDownFilter.class );

    public ContextTearDownFilter()
    {
        log.info( "ContextTearDownFilter is installed" );
    }

    /**
     * Tear down per-thread context here (see ContextCreationFilter) because
     * Tomcat likely will not clear it off our thread before putting it back into its
     * pool. This would otherwise result in a memory leak.
     */
    @Override
    public ContainerResponse filter( ContainerRequest request, ContainerResponse response )
    {
        ThreadContext.remove();
        return response;
    }

    @Override
    public ContainerRequestFilter getRequestFilter()
    {
        return null;
    }

    @Override
    public ContainerResponseFilter getResponseFilter()
    {
        return this;
    }
}

web.xml additions...

<servlet>
    <servlet-name>     Jersey REST Service                                   </servlet-name>
    <servlet-class>    com.sun.jersey.spi.container.servlet.ServletContainer </servlet-class>
    <init-param>
      <param-name>    com.sun.jersey.config.property.packages                </param-name>
      <param-value>   com.acme.web.user.service                              </param-value>
    </init-param>
    <!-- Do not disturb the order of these two request filters! -->
    <init-param>
      <param-name>    com.sun.jersey.spi.container.ContainerRequestFilters   </param-name>
      <param-value>   com.acme.web.filter.ContextCreationFilter,com.acme.web.filter.HeaderDetectionFilter </param-value>
    </init-param>
    <init-param>
      <param-name>    com.sun.jersey.spi.container.ContainerResponseFilters  </param-name>
      <param-value>   com.acme.web.filter.ContextTearDownFilter        </param-value>
    </init-param>
    <load-on-startup> 1            </load-on-startup>
</servlet>

Above, HeaderDetectionFilter is registered only to show how two or more of these filters are specified (the mechanism might not have been obvious if you've never done this before).

Appendix: a simple test

Devoid of allegiance to servlet issues, here's a short example in straight JSE.

Context.java
package example;

import java.text.SimpleDateFormat;

/**
 * Thread-safe implementation of SimpleDateFormat each thread will get its own
 * instance of SimpleDateFormat and a thread name that will not be shared with
 * other threads.
 *
 * What's happening?
 *
 * A new instance of ThreadLocal is created, colored with Context. This instance
 * records, via the overridden initialValue() method, an instance of Context that
 * happens to be initialized, if not permanently so, to some determined state.
 * To be useful, it will be necessary to allow that state to be modified all
 * during execution (and not merely, as demonstrated by this sample code, read
 * for its initial values).
 */
public class Context
{
    private String           threadName;
    private SimpleDateFormat dateFormat;

    public String           getThreadName()                              { return threadName; }
    public void             setThreadName( String threadName )           { this.threadName = threadName; }
    public SimpleDateFormat getDateFormat()                              { return dateFormat; }
    public void             setDateFormat( SimpleDateFormat dateFormat ) { this.dateFormat = dateFormat; }

    // ---------------------------------------------------------------------------------------------------
    // Above this line was our per-thread data that we want accessible at any random point in our code.
    // Below are the mechanics of implementing per-thread data in Java. See Javadoc in previous examples.
    // ---------------------------------------------------------------------------------------------------

    private static final ThreadLocal< Context > context = new ThreadLocal< Context >()
    {
        @Override
        protected Context initialValue() { return new Context(); }
    };

    public static Context get()    { return context.get(); }
    public static void    remove() { context.remove(); }
}
ExampleMain.java
package example;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Create two threads, each with its own data. Methods return elements (pieces)
 * of that per-thread (thread-safe) data.
 *
 * Output
 *
 * Setting thread name : Thread-1
 * Setting thread name : Thread-0
 * Creating SimpleDateFormat for Thread : Thread-1
 * Creating SimpleDateFormat for Thread : Thread-0
 * Thread: Thread-1 Formatted Date: 05/02/2013 17:51:00
 * Thread: Thread-0 Formatted Date: 05/02/2013 17:51:00
 * Thread: Thread-1 Formatted Date: 05/02/2013 17:51:00
 * Thread: Thread-0 Formatted Date: 05/02/2013 17:51:00
 */
public class ExampleMain
{
    public static void main( String args[] ) throws IOException
    {
        Thread t1 = new Thread( new TlTask() );
        Thread t2 = new Thread( new TlTask() );

        t1.start();
        t2.start();
    }
}

/**
 * This represents the executing code of an application making use of per-thread
 * data.
 */
class TlTask implements Runnable
{
    @Override
    public void run()
    {
        Context ctx = Context.get();        // get our thread-local store...

        // these statements represent establishing (non-initial) values in our thread-local store...
        ctx.setThreadName( Thread.currentThread().getName() );
        ctx.setDateFormat( new SimpleDateFormat( "dd/MM/yyyy HH:mm:ss" ) );

        // these statements represent obtaining values from our thread-local store...
        String           threadName = ctx.getThreadName();
        SimpleDateFormat fmt        = ctx.getDateFormat();

        System.out.println( "Setting thread name : " + threadName );
        System.out.println( "Creating SimpleDateFormat for Thread : " + threadName );

        for( int i = 0; i < 2; i++ )
        {
            Date   date       = new Date();
            String formatted  = fmt.format( date );

            System.out.println( "Thread: " + threadName + " Formatted Date: " + formatted );
        }

        Context.remove();                   // abandon our thread-local store to garbage collection...
    }
}