InheritableThreadLocal浅析

832 阅读3分钟

这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战

一、简介

​ 根据上文所述,一些线程变量可以保存在ThreadLocal中,但是如果出现本线程是一个比较耗时的线程,需要通过子线程来进行耗时操作,这时父线程的线程总不能通过参数的方式传递线程变量,那么如何将线程变量传递给子线程。在Thread中除了有属性threadLocals引用ThreadLocal.ThreadLocalMap,其实还有一个属性,也就是inheritableThreadLocalsthreadLocals的作用是保存本地线程变量,而inneritableThreadLocals的作用是传递当前线程本地变量InheritableThreadLocal到子线程的本地变量InheritableThreadLocal中,达到父子线程之间传递线程变量的目的。

二、实例

编写测试代码:使用InheritableThreadLocal创建一个保存用户名的内部线程变量。使用ThreadLocal创建一个保存密码的线程变量。并分别为这两个变量赋值。然后创建一个线程,在这个线程中通过获取用户名和密码的值来判断(这里通过打印的方式观察),由main入口进入的主线程,是否向创建的匿名子线程传递了对应的本地变量值。

public static void main(String[] args){
  InheritableThreadLocal<String> username = new InheritableThreadLocal<>();
  ThreadLocal<String> password = new ThreadLocal<>();
  username.set("zhang");
  password.set("1234");

  new Thread(new Runnable() {
    @Override
    public void run() {
      log.info(username.get());
      log.info(password.get());
    }
  }).start();
}

输出结果为:

zhang
null

所以从打印结果基本上可以得出结论:打印出了用户名说明InheritableThreadLocal是具有父子线程传递的,而获取密码的结果是空值,说明ThreadLocal不具有父子线程传递的功能。

三、原理
1. InheritableThreadLocal的实现

InheritableThreadLocal继承于ThreadLocal,并重写了ThreadLocal中的三个方法。

  • childValue:这个接口是ThreadLocal的开放接口,默认实现是抛出UnsupportedOperationException异常。实现上仅返回入参,调用上是在创建子线程时使用。
  • getMap:重写getMap,操作InheritableThreadLocal时,将只会影响到线程对象ThreadinheritableThread属性。
  • createMap:与上面的获取方法getMap情况一致,创建时同理。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}
2.线程的创建过程

跟踪线程的创建new Thread()方法。

1.进入初始化方法。

public Thread() {
  init(null, null, "Thread-" + nextThreadNum(), 0);
}

2.调用init方法。

重载对应的init方法,然后使用当前线程(父线程)获取到对应的ThreadLocalMap,然后传递给inheritableThreadLocals变量。

private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
  init(g, target, name, stackSize, null, true);
}

通过重载方法之后,最后实际调用的处理方法。parent线程为创建子线程的当前线程,也就是父线程。inheritThreadLocals的默认值是true,且父线程的inheritableThreadLocal对象不为空。 则创建当前线程的inheritableThreadLocals对象。

private void init() {
  this.name = name;
  Thread parent = currentThread();
  	
  //   ...
  if (inheritThreadLocals && parent.inheritableThreadLocals != null)
    this.inheritableThreadLocals =
    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}

3.进入创建方法createInheritedMap方法。

以父线程的inheritableThreadLocals为实例创建子线程的inheritableThreadLocals对象实现上比较简单,将父线程的inheritableThreadLocals循环拷贝给子线程。以父线程的inheritableThreadLocals为实例创建一个ThreadLocalMap对象

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
  return new ThreadLocalMap(parentMap);
}

创建ThreadLocalMap对象实例:至于这个地方为什么采用key.childValue(),内层的逻辑也仅仅是返回入参。网上有些人说是为了减轻代码的阅读难度,笔者觉得有点牵强。感觉是为了在获取过程中做一些小转换之类的?

private ThreadLocalMap(ThreadLocalMap parentMap) {
  // ...

  for (int j = 0; j < len; j++) {
    Entry e = parentTable[j];
    if (e != null) {
      ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
      if (key != null) {
        // ①
        Object value = key.childValue(e.value);
        // ...
      }
    }
  }
}

四、思考

​ 上面说父子进程通过inheritableThreadLocals属性来传递本地变量,在实际的应用场景中,一般不会以父进程直接创建子进程的方式进行一些异步或者是耗时操作的处理,一般都是采用线程池的方式,如果采用线程池那么inheritableThreadLocal还会有效吗?读者可以考虑一下,写个demo跑一下,看看具体的情况,下一篇文章将进行解答。