InheritableThreadLocal

概述

在ThreadLocal源码分析一文中我们知道了它能够提供给线程在生命周期内访问的变量。现在我们考虑一个场景,在我们的业务中有一个很耗时的操作,需要开启多个线程去提升执行的效率,并且这些子线程都需要使用到执行程中的某个变量。我们立马能够想到在创建子线程的时候将这个变量通过构造函数传递进去,这是很容易想到的方案,那么还有没有更加优雅的解决方式呢?本文要介绍的InheritableThreadLocal就是基于此场景的一种解决方案。通过它我们可以在创建子线程时,自动的将执行线程拥有的变量传递给子线程。

线程初始化

在分析InheritableThreadLocal的实现原理之前,我们需要先了解一下Thread的源码,因为这个InheritableThreadLocal的实现和ThreadLocal一样是与线程紧密相关的。通过查看Thread的源码得知原来Thread同样还拥有一个名称为inheritableThreadLocals的ThreadLocalMap类型的成员变量。接着我们看一下Thread的init方法(所有线程的构造函数最终都会调用这个方法)

1
2
3
4
5
6
7
8
9
10
11
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...省略部分代码
Thread parent = currentThread();
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...省略部分代码
}

我们发现在线程初始化时,只要执行线程的inheritableThreadLocals不为null的话(Thread的所有public构造函数在调用这个init方法时传递的boolean inheritThreadLocals都是true)就会调用ThreadLocal的静态方法createInheritedMap。这个createInheritedMap方法的作用就是构造一个新的ThreadLocalMap然后将父线程ThreadLocalMap中的Entry数组拷贝到子线程的ThreadLocalMap中。下面我们接着分析一下这个createInheritedMap方法

createInheritedMap方法

1
2
3
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}

new了一个新的ThreadLocalMap对象,入参是父线程的ThreadLocalMap(inheritableThreadLocals),看一下这个构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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的Entry数组中Entry不为null并且引用的ThreadLocal不为null的Entry,然后通过hash算法计算出下标放到新的Entry数组中。在复制value时调用了一个childValue方法,这个方法在ThreadLocal中的定义如下

1
2
3
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}

这里直接抛出了异常,因为在调用createInheritedMap方法的时候确定创建的是InheritableThreadLocal,所以此时的调用者为InheritableThreadLocal,它重写了这个方法,目的是为了提供修改父线程中的value的能力,比如说父线程中的value为1,此时创建子线程时可能需要在这个value的基础上变更为100,那么这个childValue方法就有意义了。下面我们看一下InheritableThreadLocal的源码

InheritableThreadLocal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}
}

源码很简单,重写了父类ThreadLocal的是三个方法,重写childValue的意义在于复制父线程value时能够自定义的修改这个value。重写getMap和createMap的意义在于调用ThreadLocal的set,get,remove方法时取到的或者创建的是Thread的inheritableThreadLocals变量,而不是threadLocals变量。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.example.demo;

/**
* @author zyc
*/
public class DemoApplication {

private static InheritableThreadLocal<Integer> inheritableThreadLocal1 = new InheritableThreadLocal<>();
private static InheritableThreadLocal<String> inheritableThreadLocal2 = new InheritableThreadLocal<String>() {

@Override
public String childValue(String parentValue) {
return parentValue.concat("1");
}
};

public static void main(String[] args) {
inheritableThreadLocal1.set(1);
inheritableThreadLocal2.set("1");
print();
new Thread(DemoApplication::print).start();
}

static void print() {
System.out.println(Thread.currentThread().getName() + ":" + inheritableThreadLocal1.get());
System.out.println(Thread.currentThread().getName() + ":" + inheritableThreadLocal2.get());
}

}

控制台输出

1
2
3
4
main:1
main:1
Thread-0:1
Thread-0:11

总结

InheritableThreadLocal的运行原理和ThreadLocal一致,只不过它相对于ThreadLocal多出了一项功能,当在执行线程中创建子线程时,如果执行线程的inheritableThreadLocals不为null的话(也就是通过InheritableThreadLocal给执行线程设置了值),那么子线程就会将这个inheritableThreadLocals拷贝到自己的成员变量inheritableThreadLocals中。