聊一聊线程变量绑定之InheritableThreadLocal

  • 2019 年 12 月 19 日
  • 筆記

通过上一节我们知道,ThreadLocal 可以用于线程变量绑定和隔离,但是却无法做到服务调用链路很长时,需要做链路追踪时,子线程无法获取到父线程中的共享变量的情况,本节的 InheritableThreadLocal 就是用来解决这个问题的。

示例

@Test  public void testInheritableThreadLocal() throws InterruptedException {      InheritableThreadLocal threadLocal = new InheritableThreadLocal();      threadLocal.set("simple thread local in main thread!");      Thread thread = new Thread(new Runnable() {          @Override          public void run() {              System.out.println("inner thread:" + threadLocal.get());              threadLocal.set("simple ThreadLocal in thread!");              System.out.println("inner thread:" + threadLocal.get());          }      });      thread.start();      thread.join();      System.out.println(threadLocal.get());  }

输出结果为:

inner thread:simple thread local in main thread!  inner thread:simple ThreadLocal in thread!  simple thread local in main thread!

可以看到,在子线程中拿到了父线程的 threadLocal 变量的值。

源码

我们继续按照分析 ThreadLocal 源码的思路来分析一下 InheritableThreadLocal 变量。

Thread 构造方法

public Thread() {      init(null, null, "Thread-" + nextThreadNum(), 0);  }    private void init(ThreadGroup g, Runnable target, String name,                        long stackSize) {          init(g, target, name, stackSize, null);      }    private void init(ThreadGroup g, Runnable target, String name,                        long stackSize, AccessControlContext acc) {      .....................       if (parent.inheritableThreadLocals != null)              this.inheritableThreadLocals =                  ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);      ..............  }

在 Thread 中有一个 inheritableThreadLocals 变量,它的值是在构造方法中赋值的,会将父 Thread 的 inheritableThreadLocals 变量传入到当前子线程的中,并通过 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)方法创建子线程的 inheritableThreadLocals。

ThreadLocal.createInheritedMap

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {          return new ThreadLocalMap(parentMap);      }         private ThreadLocalMap(ThreadLocalMap parentMap) {              Entry[] parentTable = parentMap.table;              int len = parentTable.length;              setThreshold(len);              table = new Entry[len];                for (int j = 0; j < len; j++) {                  Entry e = parentTable[j];                  if (e != null) {                      @SuppressWarnings("unchecked")                      ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();                      if (key != null) {                          Object value = key.childValue(e.value);                          Entry c = new Entry(key, value);                          int h = key.threadLocalHashCode & (len - 1);                          while (table[h] != null)                              h = nextIndex(h, len);                          table[h] = c;                          size++;                      }                  }              }          }

会将父线程的 ThreadLocalMap 中的 table 中的 Entry 拷到子线程的 ThreadLocalMap 中。

在这里我们尤其需要关注的是这一行:

Object value = key.childValue(e.value)

这在 InheritableThreadLocal 中有对应的实现:

protected T childValue(T parentValue) {         return parentValue;     }

直接返回的是父线程中的 value。

其他方法

getMap

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

返回的是当前线程的 inheritableThreadLocals 变量。

createMap

void createMap(Thread t, T firstValue) {          t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);      }

创建 ThreadLocalMap 的时候也是用的 inheritableThreadLocals 引用。

InheritableThreadLocal 主要用于子线程创建时,需要自动继承父线程的 ThreadLocal 变量,方便必要信息的进一步传递。

问题

我们看下下面这两个示例: 示例一:

@Test      public void testMultiThreadWithoutPool(){          InheritableThreadLocal threadLocal = new InheritableThreadLocal();          IntStream.range(0,10).forEach(i -> {              threadLocal.set(i);              new Thread(() -> {                  System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());              }).start();              try {                  Thread.sleep(1000);              } catch (InterruptedException e) {                  e.printStackTrace();              }          });      }

输出结果:

Thread-0:0  Thread-1:1  Thread-2:2  Thread-3:3  Thread-4:4  Thread-5:5  Thread-6:6  Thread-7:7  Thread-8:8  Thread-9:9

示例二:

private ExecutorService service = Executors.newFixedThreadPool(1);        @Test      public void testInheritableByThreadPool(){          InheritableThreadLocal threadLocal = new InheritableThreadLocal();          IntStream.range(0,10).forEach(i -> {              System.out.println(i);              threadLocal.set(i);              service.submit(() -> {                  System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());              });              try {                  Thread.sleep(1000);              } catch (InterruptedException e) {                  e.printStackTrace();              }          });      }

输出结果:

0  pool-1-thread-1:0  1  pool-1-thread-1:0  2  pool-1-thread-1:0  3  pool-1-thread-1:0  4  pool-1-thread-1:0  5  pool-1-thread-1:0  6  pool-1-thread-1:0  7  pool-1-thread-1:0  8  pool-1-thread-1:0  9  pool-1-thread-1:0

对比两者的结果可以发现,同样的 set 操作,结果大不相同。这是因为示例一是每次 new Thread 的操作都会将父线程的 ThreadLocal 变量传入子线程中,示例二是线程池的操作,线程只会初始化一次,子线程是取不到父线程变量的实时变动的。关于这个问题,下篇文章中将要讲的 TransmittableThreadLocal 可以完美解决。