Skip to content

面试速答(先看这里)

**一句话结论:**当我们在同一个线程中,想要共享变量的话,是可以直接使用ThreadLocal的,但是如果在父子线程之间,共享变量,ThreadLocal就不行了。

60秒标准回答:

当我们在同一个线程中,想要共享变量的话,是可以直接使用ThreadLocal的,但是如果在父子线程之间,共享变量,ThreadLocal就不行了

如以下代码,会抛出NPE

因为ThreadLocal 变量是为每个线程提供了独立的副本,因此不同线程之间只能访问它们自己的副本

**答题顺序:**结论 → 原理/机制 → 关键流程 → 场景与取舍 → 易错点

回答主线:

  • **InheritableThreadLocal:**与 ThreadLocal 不同, InheritableThreadLocal 可以在子线程中继承父线程中的值 。
  • **InheritableThreadLocal的原理:**当我们通过Thread的构造方法创建线程的时候,会调用到java.lang.Thread#init方法,这个方法中针对InheritableThreadLocal有一些特殊的处理:
  • **InheritableThreadLocal 限制:**InheritableThreadLocal是用于主子线程之间参数传递的,但是,这种方式有一个问题,那就是必须要是在主线程中手动创建的子线程才可以,而现在池化技术非常普遍了,很多时候线程都是通过线程池进行创建和复用的,这时候InheritableThreadLocal就不行了。

**记忆锚点:**InheritableThreadLocal → ThreadLocal → inheritableThreadLocals → TransmittableThreadLocal → javalangThread#init → 能访问它们自己的副本

关键取舍:

  • InheritableThreadLocal 限制 InheritableThreadLocal是用于主子线程之间参数传递的,但是,这种方式有一个问题,那就是必须要是在主线程中手动创建的子线程才可以,而现在池化技术非常普遍了,很多时候线程都是通过线程池进行创建和复用的,这时候InheritableThreadLocal就不行了。

易错提醒:

  • 这里注意一下,有可能得到的结果是 因为如果主线程先执行完,那么就是这个结果。

加分表达:

  • 当我们在同一个线程中,想要共享变量的话,是可以直接使用ThreadLocal的,但是如果在父子线程之间,共享变量,ThreadLocal就不行了。
  • 我们把开头的示例中ThreadLocal改成InheritableThreadLocal就可以了: InheritableThreadLocal的原理 当我们通过Thread的构造方法创建线程的时候,会调用到java.lang.Thread#init方法,这个方法中针对InheritableThreadLocal有一些特殊的处理: 意思就是检查父线程中是否…

追问准备:

  • 围绕「InheritableThreadLocal」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「ThreadLocal」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「inheritableThreadLocals」:底层原理是什么?使用时有哪些边界和常见坑?
  • 如果线上出现异常,你会如何定位、验证并规避?

典型回答 ​

当我们在同一个线程中,想要共享变量的话,是可以直接使用ThreadLocal的,但是如果在父子线程之间,共享变量,ThreadLocal就不行了。

如以下代码,会抛出NPE:

plain
public static ThreadLocal<Integer> sharedData = new ThreadLocal<>();

public static void main(String[] args) {
    sharedData.set(0);
    MyThread thread = new MyThread();
    thread.start();
    sharedData.set(sharedData.get() + 1);
    System.out.println("sharedData in main thread: " + sharedData.get());

}

static class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("sharedData in child thread: " + sharedData.get());
        sharedData.set(sharedData.get() + 1);
        System.out.println("sharedData in child thread after increment: " + sharedData.get());
    }
}

输出结果:

plain
sharedData in child thread: null
sharedData in main thread: 1
Exception in thread "Thread-0" java.lang.NullPointerException
	at com.hollis.java.bagu$MyThread.run(JavaBaguThreadLocalTest.java:30)

因为ThreadLocal 变量是为每个线程提供了独立的副本,因此不同线程之间只能访问它们自己的副本。

那么,想要实现数据共享,主要有两个办法,第一个是自己传递,第二个是借助InheritableThreadLocal

自己传递 ​

我们可以在子线程中创建一个成员变量,这样在主线程创建子线程的时候,可以给成员变量赋值,这样实现数据共享。

plain
import java.util.concurrent.ConcurrentHashMap;

public class SharedDataExample {
   public static void main(String[] args) {
      ConcurrentHashMap<String, String> sharedData = new ConcurrentHashMap<String, String>();
      MyThread thread = new MyThread(sharedData);
      thread.start();
      sharedData.put("key", "value");
      System.out.println("sharedData in main thread: " + sharedData.get("key"));
   }
}

class MyThread extends Thread {
   ConcurrentHashMap<String, String> sharedData;
   public MyThread(ConcurrentHashMap<String, String> data) {
      this.sharedData = data;
   }
   public void run() {
      sharedData.put("key", "new value");
      System.out.println("sharedData in child thread: " + sharedData.get("key"));
   }
}

但是一定要考虑线程安全的问题。

输出结果:

plain
sharedData in child thread: new value
sharedData in main thread: new value

这里注意一下,有可能得到的结果是

java
sharedData in main thread:  value
sharedData in child thread: new value

因为如果主线程先执行完,那么就是这个结果。

InheritableThreadLocal ​

与 ThreadLocal 不同,InheritableThreadLocal 可以在子线程中继承父线程中的值。在创建子线程时,子线程将复制父线程中的 InheritableThreadLocal 变量。

我们把开头的示例中ThreadLocal改成InheritableThreadLocal就可以了:

plain
    public static InheritableThreadLocal<Integer> sharedData = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        sharedData.set(0);
        MyThread thread = new MyThread();
        thread.start();
        sharedData.set(sharedData.get() + 1);
        System.out.println("sharedData in main thread: " + sharedData.get());

    }

    static class MyThread extends Thread {

        @Override
        public void run() {
            System.out.println("sharedData in child thread: " + sharedData.get());
            sharedData.set(sharedData.get() + 1);
            System.out.println("sharedData in child thread after increment: " + sharedData.get());
        }
    }

输出结果:

plain
sharedData in child thread: 0
sharedData in main thread: 1
sharedData in child thread after increment: 1

扩展知识 ​

InheritableThreadLocal的原理 ​

当我们通过Thread的构造方法创建线程的时候,会调用到java.lang.Thread#init方法,这个方法中针对InheritableThreadLocal有一些特殊的处理:

plain
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

意思就是检查父线程中是否有inheritableThreadLocals,如果有,就把他复制到子线程的inheritableThreadLocals中!

InheritableThreadLocal 限制 ​

InheritableThreadLocal是用于主子线程之间参数传递的,但是,这种方式有一个问题,那就是必须要是在主线程中手动创建的子线程才可以,而现在池化技术非常普遍了,很多时候线程都是通过线程池进行创建和复用的,这时候InheritableThreadLocal就不行了。

TransmittableThreadLocal是阿里开源的一个方案 ,这个类继承并加强InheritableThreadLocal类。用来实现线程之间的参数传递:

📄 ✅有了InheritableThreadLocal为啥还需要TransmittableThreadLocal?

打开文档:✅有了InheritableThreadLocal为啥还需要TransmittableThreadLocal?