InheritableThreadLocal
ThreadLocal线程间的问题:ThreadLocal无法在父子线程间进行传递。例如:主线程设置ThreadLocal,并且创建了新线程,此时新线程访问主线程设置的ThreadLocal,返回null。在线程池中更为突出,因为线程池中的业务线程是复用的,每个任务提交时,都有不同的ThreadLocal。
为了解决ThreadLocal在父子线程中的传递问题,应运而生了InheritableThreadLocal。ThreadLocal不能解决父子线程间的传递,那么另起个变量InheritableThreadLocal,创建新线程时,复制父线程的InheritableThreadLocal,通过新变量解决上述问题。
存储位置
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
inheritableThreadLocals和threadLocals,都存储在Thread线程中,只不过inheritableThreadLocals可以继承。
数据结构
查看InheritableThreadLocal就是继承自ThreadLocal,只不过修改了getMap/createMap方法,即返回不同的存储位置。
使用示例
使用范例:
public static void main(String[] args) {
InheritableThreadLocal<String> itl = new InheritableThreadLocal<>();
itl.set("age");
System.out.println(Thread.currentThread().getName() + ":" + itl.get());
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + itl.get());
itl.set("inheritable");
System.out.println(Thread.currentThread().getName() + ":" + itl.get());
}
}).start();
for (int i = 0; i < 100000; i++) {
}
System.out.println(Thread.currentThread().getName() + ":" + itl.get());
itl.set("address");
System.out.println(Thread.currentThread().getName() + ":" + itl.get());
}
输出:
main:age
Thread-0:age
Thread-0:inheritable
main:age
main:address
通过上述范例,InheritableThreadLocal实现了ThreadLocal在线程中的传递,当然也存在一些缺点。
生效原因
创建线程通过new Thread方法,而方法的底层还是调用init方法,看看init方法。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
//省略一些无关代码
//parent=主线程/父线程
Thread parent = currentThread();
//省略无关代码
//父线程的inheritableThreadLocals不为空,copy
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
//createInheritableMap
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
//new ThreadLocalMap(parentMap) 就是把ThreadLocalMap copy一份
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++;
}
}
}
}
生效的原因:子线程主动从父线程copy一份InheritableThreadLocal,这样就保证了上下文的传递。
缺点
InheritableThreadLocal也存在一些缺陷:
- copy是一次性的,只有子线程创建时,才会copy父线程,并且只copy因此。因此创建完子线程后,主线程添加的ThreadLocal,子线程获取仍然为null。
- 子线程的InheritableThreadLocal的各种操作,例如:set值,remove值,对主线程都没有影响。
- 子线程copy主线程的InheritableThreadLocal是浅拷贝,如果子线程/主线程,针对value的修改,双方都可以感知。优点:可以把通过value的变动,实现线程之间的互通。
缺点:一方对value的修改,可能会影响另一方。 - InheritableThreadLocal对线程池不支持。因为子线程的copy是一次性的,因此当创建池内线程池,会从业务线程copy一份InheritableThreadLocal,但是当池内线程执行其他业务时,就不会copy该业务提交时,业务线程的InheritableThreadLocal,同时可能因为第一次的copy导致后续的InheritableThreadLocal的污染。
public class InheritableThreadLocalDemo {
private static ExecutorService executorService = Executors.newFixedThreadPool(1);
private static InheritableThreadLocal<String> tl = new InheritableThreadLocal();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int temp = i;
//模拟10个请求
new Thread(new Runnable() {
@Override
public void run() {
tl.set("test" + temp);
System.out.println(Thread.currentThread().getName() + ":" + tl.get());
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + tl.get());
}
});
}
}).start();
}
}
}
Thread-0:test0
Thread-5:test5
Thread-4:test4
Thread-2:test2
Thread-1:test1
Thread-8:test8
Thread-9:test9
Thread-3:test3
Thread-6:test6
Thread-7:test7
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
通过输出发现,10个请求,都各自设置了自己的TTL,但是最终池内线程,只输出一个tl,这就是数据污染,因为pool-1-thread-1线程是Thread-6创建的,因此copy test6,当池线程执行其他的任务时,拿到的都是test6。
说明:为了解决InheritableThreadLocal在线程池中的缺陷,衍生了:TransmittableThreadLocal