How to shoot yourself in foot with ThreadLocals
It will start nicely. Like most of the stories. You discover a new concept and are amazed by it’s powers. And then equipped with this new hammer suddenly everything starts to look like a nail. From what we have experienced in past months, java.lang.ThreadLocal makes one hell of a hammer.
I guess it all boils down to the very concept of how ThreadLocal works. It’s easiest to grasp the concept via the scoping analogy. In the very same way your Spring beans can be for example container, session or request scoped. ThreadLocal equips you with the possibility to declare objects in Thread scope.
You can set any object to ThreadLocal and this object will have both global and local scope in the thread accessing this object. It might be complex to grasp at first but let me explain:
- Values stored in ThreadLocal are globally accessible to the thread. Meaning that if you have access to ThreadLocal reference within your code, then the values stored in it can be accessed from anywhere inside that thread. If a thread calls methods from several classes, then all the methods can see the ThreadLocal variable set by other methods (because they are executing in same thread). The value need not be passed explicitly. It’s like how you would use global variables.
- Values stored in ThreadLocal are local to the thread, meaning that each thread will have it’s own ThreadLocal variable. A thread cannot access/modify other thread’s ThreadLocal variables.
So here we have a reason to celebrate – we have a truly powerful concept in our hands. It is often the easiest way to render a stateful class thread-safe. And encapsulate non-thread-safe classes so that they can safely be used in multithreaded environments. In addition to simplicity, using ThreadLocal to store a per-thread-singleton or per-thread context information has a valuable information included – by using a ThreadLocal, it’s clear that the object stored in the ThreadLocal is not shared between threads, simplifying the task of determining whether a class is thread-safe or not. Which we have found is not an easy task when you have a codebase of 1,000,000 lines at hand.
On the other hand, this powerful concept creates numerous problems in wrong hands. Like any other abused design concept. In the past months we have most often faced two problems:
- ThreadLocal gives you the opportunity to use the variables without explicitly passing them down through the method invocation chain. Which could be useful on certain occasions. But you guys out there who have created a n-layer architecture to abstract away different communication interfaces. And then grab HttpServletRequest from ThreadLocals in your DAO objects … what were you smoking when making this decision? It took a few hours and a second pair of eyes when we were digging up this particular case. But anyhow – be careful when using the powers of globalization. You end up creating unexpected dependencies within your code. And as you might remember – this is not a wise thing to do.
- It is darn easy to introduce a memory leak to your code when using a ThreadLocal. Which serves as a nice demonstration about the complexities surrounding classloaders. If you are deploying your code in an application server then your application classes are loaded/unloaded with a different classloader than the one used by the application server itself. Which is not bad per se. But now considering that modern application servers also pool threads instead of creating a new one on each HttpRequest, we have built the foundation to a problem.
If one of the application classes stores a value in ThreadLocal variable and doesn’t remove it after the task at hand is completed, a copy of that Object will remain with the Thread (from the application server thread pool). Since lifespan of the pooled Thread surpasses that of the application, it will prevent the object and thus a ClassLoader being responsible for loading the application from being garbage collected. And we have created a leak, which has a chance to surface in a good old java.lang.OutOfMemoryError: PermGen space form.
So should we avoid using ThreadLocal considering the harm it can cause? Maybe not so fast Joshua Bloch has said half a decade ago:
“Can you cause unintended object retention with thread locals? Sure you can. But you can do this with arrays too. That doesn’t mean that thread locals (or arrays) are bad things. Merely that you have to use them with some care. The use of thread pools demands extreme care. Sloppy use of thread pools in combination with sloppy use of thread locals can cause unintended object retention, as has been noted in many places. But placing the blame on thread locals is unwarranted.”
I tend to agree to Mr. Bloch and do not think ThreadLocal is an evil creation. But I also do think it is a concept which many fail to understand properly.
Inspiration for this article was mainly gathered during sleepless hours of tracing down some nasty bugs. But while writing, following resources proved to be beneficial as well:
- Veera Sundar blogpost explaining scoping
- Javin Paul summary about ThreadLocal leaks
- Joshua Bloch opinion on ThreadLocals
Reference: How to shoot yourself in foot with ThreadLocals from our JCG partner Nikita Salnikov Tarnovski at the Plumbr Blog blog.
> And then grab HttpServletRequest from ThreadLocals in your DAO objects Haha, yes. Who hasn’t seen those in the wild. The authors are often no longer to be found :-) ThreadLocals have saved me numerous times when implementing JDBC SQLData types (for Oracle). The JDBC API really sucks as it doesn’t give access to the JDBC Connnection when you serialise nested collections of object types. I.e. the API by itself is unusable, people have to resort to global temporary variables. Also, when allocating LOBs and later calling free() on them, you cannot properly implement that without ThreadLocal. At least, that’s… Read more »
Whenever I felt the urge to use ThreadLocal I could realize that it is because some architectural mistake. If it is your mistake, you are in a lucky position: you can fix that and avoid ThreadLocal. If it is not your code, like the example Lukas mentioned: you have no choice other than use ThreadLocal to have a workaround.
Somebody said that you should use thread local very careful. My opinion is that there is no need for extra care or carefulness. You just have to know what you do.