InheritableThreadLocal and Bugs
I was at Devoxx last week, where I enjoyed several talks about the new and upcoming Concurrency features in Java. Maybe I’ll do an overview of those in a future post, but if I’m being honest, they’re already pretty well described elsewhere.
In one of the talks, the speaker mentioned the venerable InheritableThreadLocal
, which reminded me of an interesting bug I’d recently encountered, which I’ll share here for posterity.
There are two types of thread-local variables in Java. The regular, ThreadLocal
, and the oddly named InheritableThreadLocal
. I say “variables”, but really they’re just classes implementing a bit of magic, but that’s not important right now. ThreadLocal
is pretty well known, and simply ensures that every thread gets its own private version of a variable, isolated from all other threads. This is often used to store the current user in a globally accessible context, or to create caches of things that are not thread-safe. InheritableThreadLocal
is very similar, in fact it extends ThreadLocal. Going by the name, you might be tempted to think that it has something to do with inheritance in the OO-sense, but you’d be wrong. Instead, it refers to what happens to the thread-local value when the owning thread spawns another thread.
With a regular ThreadLocal
, nothing happens. The new thread will see the default value and can set a specific value whenever it wants to. With an InheritableThreadLocal
, the value of the parent thread is assigned as the new thread’s initial value.
This could be useful when you want to propagate that current user to any threads that are spawned on behalf of that user. But could be very dangerous if you were using a ThreadLocal
to cache something non-thread-safe. Which is probably why this is not the default behaviour for all thread-locals.
So far, so good, mostly. This is just the way things work. The bug was caused by misusing ThreadLocal.withInitial()
. This is a convenience method that assigns a default value to a ThreadLocal
for each new thread.
// This is fine: "bar" will be the default value for foo for every thread
static ThreadLocal<String> foo = ThreadLocal.withInitial(() -> "bar");
// And this is wrong
static ThreadLocal<String> inheritable = InheritableThreadLocal.withInitial(() -> "qux");
The above example compiles, and will seem to work as long as you don’t look too closely. However, any value set by a specific thread will not be inherited by any child threads it might spawn! This is because withInitial
is a static method on ThreadLocal
and not on InitialThreadLocal
. It creates a plain old ThreadLocal
with the vanilla behaviour.
This would have failed to compile had the left-hand side been more strict with its typing:
// This does not compile
static InheritableThreadLocal<String> inheritable = InheritableThreadLocal.withInitial(() -> "qux");
// Type mismatch: cannot convert from ThreadLocal<String> to InheritableThreadLocal<String>
I don’t know whether this was a deliberate design decision, or whether this was an oversight when withInitial
was added back in Java 8. In any case, if you want to combine inheritability with default values, the solution is pretty easy but also pretty ugly.
static InheritableThreadLocal<String> inheritable = new InheritableThreadLocal() {
protected String initialValue() {
return "qux";
}
};
This is definitely an API gotcha, but it’s an API that isn’t widely used, so hopefully it doesn’t cause too many problems out in the wild.
— Elric