理解 Java 中的 InheritableThreadLocal

huangapple go评论77阅读模式
英文:

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 on
  • p3.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(&quot;p1&quot;); 
        pt.start(); 
    } 
}

class ParentThread extends Thread { 
	static int childCount = 0;

    public static InheritableThreadLocal threadId = new InheritableThreadLocal() {
        public Object childValue(Object parentValue) 
        { 
            return parentValue.toString() + &quot;.c&quot; + (++childCount) ;  //this is line 18 where NullPointerException is occuring
        }
    }; 
    
	public ParentThread(String pThreadId) {
		threadId.set(pThreadId);
	}
	
    public void run() 
    { 
        System.out.println(&quot;Thread id:&quot; + threadId.get());
        
        ChildThread childThread1 = new ChildThread(); 
        childThread1.start(); 
        
        ChildThread childThread2 = new ChildThread(); 
        childThread2.start();
    } 
} 
class ChildThread extends Thread { 
  
    public void run() 
    { 
        System.out.println(&quot;Child Thread Value :&quot; + ParentThread.threadId.get()); 
    } 
} 

It prints:

Thread id:null
Exception in thread &quot;Thread-0&quot; java.lang.NullPointerException
	at com.mahesha999.examples.java.multithreading.ParentThread$1.childValue(InheritableThreadLocalDemo.java:18)
	at java.lang.ThreadLocal$ThreadLocalMap.&lt;init&gt;(Unknown Source)
	at java.lang.ThreadLocal$ThreadLocalMap.&lt;init&gt;(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.&lt;init&gt;(Unknown Source)
	at com.mahesha999.examples.java.multithreading.ChildThread.&lt;init&gt;(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 on
  • p3.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. 父线程
  3. 子线程1
  4. 子线程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:

  1. Main Thread
  2. Parent Thread
  3. Child Thread #1
  4. 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&lt;String&gt; local = new InheritableThreadLocal&lt;String&gt;() {
            public String childValue(String parentValue) {
                return parentValue.toString() + &quot;.c&quot; + (childCount.incrementAndGet());
            }
        };
        
        final Runnable child = () -&gt; {
            System.out.println(&quot;Child Thread Value: &quot; + local.get());
        };
        
        final Runnable parent = () -&gt; {
            // The thread local value is associated with the thread that is running this block of
            // code.
            local.set(&quot;p1&quot;);
            System.out.println(&quot;Thread id:&quot; + 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&lt;ThreadLocal, Object&gt; threadLocals;
	// getter
	Map&lt;ThreadLocal, Object&gt; 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(&quot;Thread id:&quot; + 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.

huangapple
  • 本文由 发表于 2020年4月5日 05:08:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/61034815.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定