【问题标题】:Understanding InheritableThreadLocal in Java了解 Java 中的 InheritableThreadLocal
【发布时间】:2020-04-04 21:08:22
【问题描述】:

我希望 ParentThreadthreadId 设置为传递给其构造函数的值(例如 p1)。然后它的所有孩子都将threadId 设置为p1.c1p1.c2 等等。我写了以下代码:

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()); 
    } 
} 

打印出来:

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)

我在eclipse中调试,发现里面的构造函数threadId被正确设置为p1,但是在ParentThread.run()里面,读作null。此外,在 childValue 内部,.toString() 被调用 null,抛出 NullPointerException

Q1. *为什么构造函数中设置的值在run()childValue()中不可见?

我也想要这个ParentClass 可重复使用,也就是说我应该能够拥有类似的系列

  • p2.c1p2.c2
  • p3.c1p3.c2
  • 等等

我觉得,为此,我应该将childCountthreadId 设为非静态。但是从这两个变量中删除 static 会给我编译时错误

Cannot make a static reference to the non-static field ParentThread.threadId

ChildThread.run() 内,用于ParentThread.threadId.get()。但是,现在我无法想象如何使这些 InheritableThreadLocal 变量在每个实例的每个线程中唯一。

Q2.我该怎么做?

Q3. 还是在这里遗漏了一些愚蠢的东西? “每个实例每个线程唯一”只不过是“每个实例唯一”,在这种情况下我们根本不需要线程本地的概念吗?这就是为什么所有ThreadLocal 变量都应该按约定为static 的原因吗?

【问题讨论】:

    标签: java multithreading thread-local inheritable-thread-local


    【解决方案1】:

    我认为您的困惑是认为 ThreadLocal 与您嵌入它的 Thread 对象相关联。它与调用线程本地方法时的 current thread 相关联。您在这里有 4 个线程:

    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 和父 Runnable 之间共享。在后台,这些 lambdas 将本地引用的 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);
    

    【讨论】:

    • 我也在努力理解如何使用 ThreadLocal 的:通过将它们设为 static 类级别,将它们设为 non-static 实例级别,然后,在上面你给出了本地方法级别的代码! !
    • localInheritableThreadLocalDemo.main() 是本地的,这意味着 InheritableThreadLocalDemo 的任何其他方法都无法访问它。我猜当被parentchild 继承时,它的作用域是本地还是实例或类级别的线程本地?我猜他们将是parentchild 内部的实例级别,对吧?
    • Re,“……正在运行的线程。”在有四个或八个 CPU 的系统上,哪个线程是“正在运行的线程”,它们都是属于同一个程序的活跃运行的线程?不幸的是,很多 OS API 文档都使用“当前线程”这个短语,因为它在您阅读 OS 源代码时有意义, 但它确实没有您正在编写应用程序代码。您在 API 文档中看到“当前线程”的任何地方,都应该自动将其读作“调用线程”。
    【解决方案2】:

    让我们看看相关的代码部分。

    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 虽然它提供了娱乐,但我会尖叫。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-09
      • 2012-10-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多