英文:
Understanding InheritableThreadLocal in Java
问题
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 debugged in Eclipse and found that inside constructor threadId
is correctly set to p1
, but inside ParentThread.run()
, it's read as null
. Also, inside childValue
, .toString()
is called on null
, throwing NullPointerException
.
Q1. Why the value set in the 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 on- and so on
I feel, for this, I should make childCount
and threadId
non-static. But removing static
from both these variables gives me a 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 I can make these InheritableThreadLocal
variables unique per thread per instance.
Q2. How should I do this?
Q3. Or am I missing something stupid here? Is "unique per Thread per instance" nothing but "unique per instance only," and we don't need the concept of thread-local at all in this case? Is this why all ThreadLocal
variables should be static
by convention?
英文:
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 on- and so on
I 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?
答案1
得分: 2
我认为你的困惑在于认为ThreadLocal与嵌套其中的Thread对象相关联。实际上,它与调用线程局部方法时的当前线程相关联。在这里有四个线程:
- 主线程
- 父线程
- 子线程1
- 子线程2
调用为父对象调用线程局部设置方法的构造函数在主线程中调用(#1),为该线程设置线程局部,而不是父线程(#2),因为父线程尚未开始运行。
以下是一个在线程类中未覆盖任何内容的最小工作示例:
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();
}
}
在此示例中,存在一个单一的线程局部实例,它在子线程和父线程的Runnable之间共享。在底层,这些Lambda表达式将本地引用的threadLocal存储为成员字段,您可以在调试期间检查该字段。但是,这方面的作用范围是独立的。
将线程局部视为指向每个线程映射结构的键。
class Thread {
Map<ThreadLocal, Object> threadLocals;
// getter
Map<ThreadLocal, Object> getThreadLocals() { return threadLocals; }
}
每个不同的ThreadLocal实例本身都是该结构中的一个键。当前线程被定义为调用Thread.currentThread()的结果。因此,每次调用threadLocalInstance.get()时,它实际上在执行以下操作:
Object thisThreadValue = Thread.currentThread().getThreadLocals().get(threadLocalInstance);
英文:
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:
- Main Thread
- Parent Thread
- Child Thread #1
- Child Thread #2
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);
答案2
得分: 0
让我们看看代码的相关部分。
class ParentThread extends Thread {
// ...
public ParentThread(String pThreadId) {
threadId.set(pThreadId);
}
所以 threadId
将会被设置给运行构造函数的 Thread.currentThread()
。这不能是正在被构造的实例。
public void run()
{
System.out.println("Thread id:" + threadId.get());
现在在可能是新线程中,threadId
不会被初始化。
ChildThread childThread1 = new ChildThread();
在这里,null
被解引用。
一般情况下,我会避免使用 ThreadLocal
,以及与全局变量相关的大多数其他内容。InheritableThreadLocal
我会尖叫着逃离,尽管它提供了娱乐。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论