You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

继承Thread的类中InheritableThreadLocal字段赋值异常问题求助

问题分析:InheritableThreadLocal在Thread子类构造方法中赋值的异常行为

异常现象

当在继承Thread的类的对象创建阶段为InheritableThreadLocal字段设置值时,出现不符合预期的行为:

  • 执行该线程的run()方法时,无法获取到构造方法中设置的值
  • 后续创建并运行该类的子类线程时,却能获取到之前父类对象创建时设置的值

复现代码

public class InheritableThreadLocalExample {

  public static void main(String[] args) {
    ThreadOne threadOne = new ThreadOne("user-one", "thread-one");
    threadOne.start();
    ThreadTwo threadTwo = new ThreadTwo("user-two", "thread-two");
    threadTwo.start();
  }

  public static class ThreadOne extends Thread {

    protected static final InheritableThreadLocal<String> USERNAME = new InheritableThreadLocal<>();

    public ThreadOne(String username, String threadName) {
      super(threadName);
      USERNAME.set(username);
    }

    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName() + ". Username: " + USERNAME.get());
    }
  }

  public static class ThreadTwo extends ThreadOne {
    public ThreadTwo(String username, String threadName) {
      super(username, threadName);
    }
  }

}

输出结果

thread-two. Username: user-one
thread-one. Username: null

原因解析

1. InheritableThreadLocal的继承时机是线程实例创建时

Java中,创建Thread实例时,会在Thread.init()方法中完成父线程(此处为主线程)的inheritableThreadLocals到新线程的复制操作,这个过程发生在线程对象构造阶段,而非start()调用时。

2. 构造方法的执行线程是主线程,而非新线程本身

ThreadOneThreadTwo的构造方法都在主线程中执行:

  • 创建threadOne时,先调用super(threadName)触发Thread初始化,此时主线程的USERNAME还未赋值,所以threadOne线程自身的inheritableThreadLocalsUSERNAME的值为null;之后执行USERNAME.set("user-one"),实际是把值存在主线程inheritableThreadLocals中,和threadOne线程自身的存储无关。
  • threadOne.start()后,run()方法调用USERNAME.get(),获取的是threadOne线程自身的inheritableThreadLocals值,自然为null

3. 静态InheritableThreadLocal是类级别的共享变量

USERNAMEstatic final修饰,属于ThreadOne类的静态成员,所有ThreadOneThreadTwo实例共享同一个InheritableThreadLocal对象,但每个线程的inheritableThreadLocals存储是独立的:

  • 创建threadTwo时,先调用super(threadName)触发Thread初始化,此时主线程的USERNAME已经被threadOne的构造方法设置为user-one,所以threadTwo线程自身的inheritableThreadLocals会复制这个值;之后执行USERNAME.set("user-two"),只是更新了主线程inheritableThreadLocals值,不会影响已经复制到threadTwo线程中的值。
  • threadTwo.start()后,run()方法获取的是threadTwo线程自身复制的user-one,因此输出该值。

内容的提问来源于stack exchange,提问作者bepopov

火山引擎 最新活动