Thread Local: A Convenient Abomination.

Posted by Uncle Bob on 09/04/2007

ThreadLocal variables are a wonderfully convenient way to associate data with a given thread. Indeed, frameworks like Hibernate take advantage of this to hold session information. However, the practice depends upon assumption that a thread is equivalent to a unit-of-work. This is a faulty assumption

Thirteen years ago, while working on my first book, Jim Coplien and I were having a debate on the nature of threads and objects. He made a clarifying statement that has stuck with me since. He said: “An object is an abstraction of function. A thread is an abstraction of schedule.”

It has become the norm, in Java applications, to assume that there is a one-to-one correspondence between a thread, and a unit-of-work. This appears to make sense since every Servlet request has it’s own particular thread. Framework authors have built on this assumption by putting unit-of-work related information (e.g. session) into ThreadLocal variables.

Clearly, the more ThreadLocal variables that hold unit-of-work related information, the more that the thread and the unit-of-work are related. While very convenient, the basic assumption is dead wrong.

A unit-of-work is a task to be performed. There is no rule that says that this task must be single threaded. Nor is there any rule that says that the components of the task must run at the same priority. Indeed, it is not uncommon for a task to have a high-priority event driven part, and a low priority compute-bound part.

Consider, for example, a unit-of-work that makes a request from a third-party-service, and then processes the response with a complex number-crunching algorithm. We want the request to run at a high priority so that it is not waiting for other low priority compute-bound tasks to finish. On the other hand, we want the number-cruncher to run at a low priority so that it yields the processor as soon as possible whenever one of the high priority requests needs a few cycles.

Clearly these two tasks form a complete unit-of-work, but just as clearly they should run as two different threads—probably with a queue in between them in a standard producer-consumer model.

Where are the unit-of-work related variables? They can’t be kept in a ThreadLocal since each part of the task runs in a separate thread. They can’t be kept in static variables since there is more than one thread. The answer is that they have to be passed around between the threads as function arguments on the stack, and recorded in the data structured placed on the queue.

So, though convenient, ThreadLocal variables confuse the issue of separating function from schedule. They tempt us to couple function and schedule together. This is unfortunate since the correspondence of function and schedule is weak and accidental.

What we’d really like is to be able to create UnitOfWorkLocal variables.