ThreadLocal is a mechanism that can be used to put data onto a thread so that it can be accessed from any code using that thread. One use is to make contextual information relevant to the current thread available throughout the stack trace, for example a transaction context, or security context.
If you use it yourself, you need to watch out if the environment in which it runs gets its
threads from a thread pool (e.g. if your application runs in a managed container such as in an app server).
Typically a thread pool gives no guarantee that the ThreadLocal (or indeed other internal state) will be cleared next time the thread is taken from the thread pool. This means you could have an issue – consider this case:
Two web applications deployed in the same EAR (or even seperate EARs depending upon your class loader configuration and deployment) – both set a thread local which is a map (key / value pairs). One application, in a given case, processing a web request doesn’t set a value in the map and calls a service which reads that unset value using a key (the same key is used within both applications). Instead of being unset (null / empty), that unset value could be a value from the other application, if the thread is not new and your code does not clean up the thread local before returning the thread to the pool.
I did some tests and these were the results:
- Tomcat 5.5 – two web applications deployed independently: No problem because each web app uses its own class loader, so the thread local is unique to each application.
- JBoss 4.2 – two EARs deployed independently: PROBLEM – it appears that both EARs are using the same class loader for the thread local, so it is shared between the applications.
- Websphere 6.1 – two ears deployed independently: No problem, UNLESS you use the PARENT_FIRST class loader setting AND the thread local is in a library on a shared classpath (i.e. the thread local is loaded by a shared class loader like the Websphere Extensions Classloader rather than the CompoundClassLoader used for applications. The same problem could occur if the WebSphere is configured to use the same class loader for all web applications.
Here are some rules which should help you to use ThreadLocal safely in an environment which uses thread pools:
- Only use ThreadLocals where you are certain the context is within a single call to the server (e.g. it cannot be used to store state between calls).
- If you use a ThreadLocal, ensure you set it somewhere in the call chain – you cannot assume it is unset, just because you did not set it in the current call (this is similar to the Java Compiler forcing you to initialise variables)
- Clear up your ThreadLocal before it is put back in the thread pool, e.g. at the end of a doPost() method in a servlet.
- If your ThreadLocal is a map of properties, then use keys which are unique to the application (e.g. they contain the application name in them).
Another interesting point is that you aren’t really meant to be doing things with threads within an application server anyway and as one of the postings below points out, Sun never really intended for ThreadLocals to be used in such environments.
A different approach to using ThreadLocal is to have a class in your application which contains a static Map where the key is a reference to the thread and the value is whatever you would otherwise have put in a ThreadLocal. So in this approach, you do not use ThreadLocal, you use the static Map. To get the data which is relevant to the current thread, you just do something like (assuming the data in the static map is another map):
Map<String, String> localData = MyThreadLocalData.
getStaticMap().get(Thread.currentThread());
String data = localData.get(keyContainingApplicationName);
The problems with this approach are:
- You have references to threads which may eventually be released from the thread pool, but which cannot be garbage collected until you remove the reference to them in your static map. So ENSURE you clean up the static map when you are done with thread (or write a routine to automatically clear up – which gets a reference to all threads in the system from Thread.getAllStackTraces().entrySet() – any references to threads you have which are not in that set can be removed). If the static map is a WeakHashMap, then this problem shouldn’t occur (thanks to Heinz Kabutz for the tip).
- If the MyThreadLocalData is loaded by a class loader which is shared between applications, you have the danger that one application can update another… (hence the reason to use application specific keys in the map of data contained in the static map).
Attached here, is a ZIP containing two EARs which I used to test all of this. The bored yet interested reader may download, study and play with them as an exercise… Have fun!
PS – Here are some other postings which back up these statements:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6254531
http://osdir.com/ml/java.jsr.166-concurrency/2005-04/msg00046.html
http://www.devwebsphere.com/devwebsphere/2005/06/dont_use_thread.html
UPDATE: Heinz Kabutz has written in a lot more depth about this problem in his newsletter: http://www.javaspecialists.eu/archive/Issue164.html