一、前言
日常工作中,经常使用ThreadLocal
来避免线程并发问题,每个线程访问自己的本地变量,没有竞争,没有锁,非常高效。现在有一个业务场景,需要创建一些子线程来执行任务,父线程中设置了ThreadLocal
的值,想在子线程中获取,能获取到吗?答案是:不能。
了解ThreadLocal
的原理,这个问题就很弱智,用脚后跟想,父线程中set
,那么这个存放值的ThreadLocalMap
就在父线程内,子线程的threadLocals
是个null
,怎么可能从子线程 get 到父线程 set 的值呢?
但是需求就要这样,该如何实现?将父线程的ThreadLocalMap
复制一份给子线程?没错,java
官方也是这么想的!
二、InheritableThreadLocal
1、使用方式
java 官方提供了一个类InheritableThreadLocal
,使用方式上和ThreadLocal
完全一样,就是类名不一样。将ThreadLocal
替换为InheritableThreadLocal
,就可以从子线程get
到父线程set
的值了。
2、继承关系
InheritableThreadLocal
是如何做到的呢?源码底下见真知:
package java.lang;
/**
* @author Josh Bloch and Doug Lea
* @see ThreadLocal
* @since 1.2
*/
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);
}
}
复制代码
InheritableThreadLocal
继承自ThreadLocal
,重写了三个方法childValue
、getMap
、createMap
,用到Thread
的一个变量inheritableThreadLocals
。那就是InheritableThreadLocal
初始化的ThreadLocalMap
赋值给t.inheritableThreadLocals
,set
和get
也是操作t.inheritableThreadLocals
。
public class Thread implements Runnable {
... ...
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
... ...
}
复制代码
3、复制原理
那是如何将父线程的 map 复制给子线程的呢?
追溯到Thread
初始化,会调用一个init()
,init
初始化的东西较多,直接看重点:
真相了,创建子线程时,默认inheritThreadLocals=true
,父线程即当前线程的inheritableThreadLocals!=null
,则将父线程的inheritableThreadLocals
复制给子线程。
// java.lang.ThreadLocal#createInheritedMap
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
复制代码
//java.lang.ThreadLocal.ThreadLocalMap#ThreadLocalMap
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) {
// InheritableThreadLocal重写了childValue
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++;
}
}
}
}
复制代码
三、childValue 的用意
ThreadLocal
中childValue
没有给出具体实现,而在InheritableThreadLocal
中也只是简单实现了下。
//java.lang.ThreadLocal#childValue
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
复制代码
//java.lang.InheritableThreadLocal#childValue
protected T childValue(T parentValue) {
return parentValue;
}
复制代码
从父线程复制ThreaLocalMap
到子线程时,值从childValue
函数过了一遍再赋值给Entry
,是何意图?关键是InheritableThreadLocal
也没做什么,但是不难猜出,ThreadLocal
留了一个childValue
就是让InheritableThreadLocal
实现的,虽然InheritableThreadLocal
没做什么,但是使用者可以继承InheritableThreadLocal
重写childValue
,对value
做特殊处理。为什么可能要对value
做特殊处理呢?
比如,设置的值是一个自定义的引用类型,那么从父线程复制到多个子线程的值就存在并发问题(值传递,地址值是共享的),所以复制的时候要保证复制给每个子线程的地址值不一样,继承InheritableThreadLocal
实现childValue
的深拷贝,定制化一个自己的InheritableThreadLocal
:
public class MyInheritableThreadLocal<T> extends InheritableThreadLocal<T>{
protected T childValue(T parentValue) {
System.out.println("MyInheritableThreadLocal。。。");
// 深拷贝
Gson gson = new Gson();
String s = gson.toJson(parentValue);
return (T)gson.fromJson(s, parentValue.getClass());
}
}
复制代码
public class InheritableThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
InheritableThreadLocal<Stu> itl = new MyInheritableThreadLocal<Stu>();
itl.set(new Stu());
System.out.println("父线程:" + itl.get().toString());
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程1:" + itl.get().toString());
}
});
thread1.start();
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程2:" + itl.get().toString());
}
});
thread2.start();
}
static class Stu {
private String name = "xxx";
}
}
// 控制台打印
父线程:com.stefan.DailyTest.InheritableThreadLocalTest$Stu@49476842
MyInheritableThreadLocal。。。
子线程1:com.stefan.DailyTest.InheritableThreadLocalTest$Stu@7446b2ac
MyInheritableThreadLocal。。。
子线程2:com.stefan.DailyTest.InheritableThreadLocalTest$Stu@75f4c190
复制代码
四、总结
InheritableThreadLocal
可以实现子线程获取父线程的本地变量。
子线程初始化时,若父线程(当前线程)的本地变量inheritableThreadLocals
不为 null,则复制给子线程。
ThreadLocal
留个childValue
的用意,就是让InheritableThreadLocal
实现,并且可以让客户端自定义重写childValue
对从父线程复制到子线程的值做特殊处理。
若父线程使用InheritableThreadLocal
设置了自定义引用类型的值,复制给子线程时存在并发问题,需要自行实现childValue
的深拷贝。
抛个问题:
如果使用线程池创建子线程,子线程只会初始化一次,父线程中使用InheritableThreadLocal
设置值,因为复制机制是在线程初始化的时候,那么父线程只有在线程池初始化子线程时同步复制一次数据,后续父线程再修改值,就无法同步更新到线程池中的子线程了,这该怎么办呢?
只要在每次提交任务时复制就可以了,这就要对线程池以及InheritableThreadLocal
做一些定制化处理,让复制机制放在每次提交任务的时候,阿里有一个开源项目给出了解决方案https://github.com/alibaba/transmittable-thread-local,后续可深入了解其实现原理。
PS: 如若文章中有错误理解,欢迎批评指正,同时非常期待你的评论、点赞和收藏。我是徐同学,愿与你共同进步!
评论