Reputation: 2979
I wanted a ParentThread
to have threadId
set to the value (say p1
) passed to its constructor. Then all its child have threadId
set to p1.c1
, p1.c2
and so on. I wrote following code:
public class InheritableThreadLocalDemo {
public static void main(String[] args)
{
ParentThread pt = new ParentThread("p1");
pt.start();
}
}
class ParentThread extends Thread {
static int childCount = 0;
public static InheritableThreadLocal threadId = new InheritableThreadLocal() {
public Object childValue(Object parentValue)
{
return parentValue.toString() + ".c" + (++childCount) ; //this is line 18 where NullPointerException is occuring
}
};
public ParentThread(String pThreadId) {
threadId.set(pThreadId);
}
public void run()
{
System.out.println("Thread id:" + threadId.get());
ChildThread childThread1 = new ChildThread();
childThread1.start();
ChildThread childThread2 = new ChildThread();
childThread2.start();
}
}
class ChildThread extends Thread {
public void run()
{
System.out.println("Child Thread Value :" + ParentThread.threadId.get());
}
}
It prints:
Thread id:null
Exception in thread "Thread-0" java.lang.NullPointerException
at com.mahesha999.examples.java.multithreading.ParentThread$1.childValue(InheritableThreadLocalDemo.java:18)
at java.lang.ThreadLocal$ThreadLocalMap.<init>(Unknown Source)
at java.lang.ThreadLocal$ThreadLocalMap.<init>(Unknown Source)
at java.lang.ThreadLocal.createInheritedMap(Unknown Source)
at java.lang.Thread.init(Unknown Source)
at java.lang.Thread.init(Unknown Source)
at java.lang.Thread.<init>(Unknown Source)
at com.mahesha999.examples.java.multithreading.ChildThread.<init>(InheritableThreadLocalDemo.java:37)
at com.mahesha999.examples.java.multithreading.ParentThread.run(InheritableThreadLocalDemo.java:30)
I debuged in eclipse and found that inside constructor threadId
is correctly set to p1
, but inside ParentThread.run()
, its read as null
. Also, inside childValue
, .toString()
is called on null
, throwing NullPointerException
.
Q1. *Why the value set in constructor is not visible inside run()
and childValue()
?
Also I want this ParentClass
reusable, that is I should be able to have series like
p2.c1
, p2.c2
and so onp3.c1
, p3.c2
and so onI feel, for this, I should make childCount
and threadId
non-static. But removing static
from both these variables gives me compile time error
Cannot make a static reference to the non-static field ParentThread.threadId
inside ChildThread.run()
for ParentThread.threadId.get()
. But, now I am not able to imagine how can I make these InheritableThreadLocal
variables unique per thread per instance.
Q2. How should I do this?
Q3. Or am missing something stupid here? Is "unique per Thread per instance" is nothing but "unique per instance only" and we dont need the concept of thread-local at all in this case? Is this why all ThreadLocal
variables should be static
by convention?
Upvotes: 2
Views: 12425
Reputation: 712
I think your confusion is thinking a ThreadLocal is associated with a Thread object that you embed it in. It is associated with whatever the current thread is when the thread local methods are called. Your have 4 threads here:
The constructor that calls the thread local set method for the Parent Object is called within the Main Thread (#1), setting the thread local for that thread not the Parent Thread (#2) because it has not yet started running.
Here's a minimal working example without overriding anything in the thread class:
package foo;
import java.util.concurrent.atomic.AtomicInteger;
public class InheritableThreadLocalDemo {
public static void main(String[] args) {
final AtomicInteger childCount = new AtomicInteger();
final InheritableThreadLocal<String> local = new InheritableThreadLocal<String>() {
public String childValue(String parentValue) {
return parentValue.toString() + ".c" + (childCount.incrementAndGet());
}
};
final Runnable child = () -> {
System.out.println("Child Thread Value: " + local.get());
};
final Runnable parent = () -> {
// The thread local value is associated with the thread that is running this block of
// code.
local.set("p1");
System.out.println("Thread id:" + local.get());
Thread c1 = new Thread(child);
c1.start();
Thread c2 = new Thread(child);
c2.start();
};
final Thread parentThread = new Thread(parent);
parentThread.run();
}
}
In this example there is a single thread local instance that is being shared between the child and parent Runnables. Under the hood these lambdas store the locally referenced threadLocal as a member field which you can inspect during debugging. But the scoping aspect to this is separate.
Think of a thread-local like a key into per-thread map structure.
class Thread {
Map<ThreadLocal, Object> threadLocals;
// getter
Map<ThreadLocal, Object> getThreadLocals() { return threadLocals; }
}
Each different ThreadLocal instance is itself a key into this structure. The current thread is defined as the result of the call Thread.currentThread(). So, think of every time you call threadLocalInstance.get() it is doing this:
Object thisThreadValue = Thread.currentThread().getThreadLocals().get(threadLocalInstance);
Upvotes: 3
Reputation: 147164
Let's look at the relevant sections of code.
class ParentThread extends Thread {
// ...
public ParentThread(String pThreadId) {
threadId.set(pThreadId);
}
So threadId
will be set for the Thread.currentThread()
that runs the constructor. This cannot be the instance that is being constructed.
public void run()
{
System.out.println("Thread id:" + threadId.get());
Now in presumably the new thread, threadId
will not be initialised.
ChildThread childThread1 = new ChildThread();
And here the null
is dereferenced.
Generally I would avoid ThreadLocal
, and most other things associated with global variables. InheritableThreadLocal
I would run screaming from though it has provided entertainment.
Upvotes: 0