1. TL功能及其实现原理
1.1 概述
ThreadLocal本意上就是线程私有的数据,每个线程维护着自己的一份副本,线程与线程之间数据隔离。
1.2 实现原理
ThreadLocal的本质其实就是存储在Thread中的成员变量threadLocals中, 我们结合源码来分析, 发现其实就是一个Map
public class Thread implements Runnable {
// 省略其他代码......
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class.
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
}
static class ThreadLocalMap {
/**
* 注意: 这里是一个软引用
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
// set值
public void set(T value) {
Thread t = Thread.currentThread();
// 获取当前线程的Map
ThreadLocalMap map = getMap(t);
if (map != null)
// 如果上一步获取的不为空, 则将当前TL和值存储到Map中
map.set(this, value);
else
// 否则, 就创建一个新的Map
createMap(t, value);
}
// 创建Map
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// get Map中的值
public T get() {
Thread t = Thread.currentThread();
// 获取当前线程的Map
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
// 通过 ThreadLocal 在当前 Map 中获取值, 如果有的情况,
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 否则, 初始化一个空值
return setInitialValue();
}
// remove 当前Map中的ThreadLocal
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
1.3 局限性
问题1: 父线程无法通过ThreadLocal向子线程传递线程私有数据
问题2: 父线程无法通过ThreadLocal向线程池中线程传递线程私有数据
2. ITL功能及其实现原理
2.1 概述
InheritableThreadLocal 是 ThreadLocal 的为了解决上述问题1的解决方案
2.2 实现原理
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
// 在第一次set() 的时候, 创建ThreadLocalMap, 将会调用子类的创建方法进行创建
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
// 线程初始化
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// 省略其他代码......
// 子线程在初始化的时候, 会给当前子线程中的inheritableThreadLocals进行赋值
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
}
// 将父线程中的table数据, 挨个复制到子线程中, 至此就实现了父线程向子线程数据传递
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++;
}
}
}
}
这意味着是:浅拷贝,即默认情况下,父线程传递给子线程的线程私有数据是一份浅拷贝,若拷贝指向个不可变对象,这不是什么问题;若指向一个引用,那尤其需要注意,因为不再有线程封闭,父子线程中任意的变更均会影响到对方。我们说它是可重载方法,意味着若要实现"Deep Copy",需要自行继承InheritableThreadLocal类并重写childValue方法
2.3 总结
小结一下解决方案背后的原理:使用InheritableThreadLocal,会将线程私有数据存储在inheritableThreadLocals指向的ThreadLocalMap中;在构造子线程时,将当前线程inheritableThreadLocals里的数据(ThreadLocalMap)"拷贝"给子线程的ThreadLocalMap,子线程因此可以通过tl.get()取到数据,如此便实现了父线程向子线程传递线程私有数据
2.4 局限性
问题1: 父线程无法通过ThreadLocal向线程池中线程传递线程私有数据
3. TTL功能及其实现原理
3.1 概述
在日常开发过程中,由于构造与销毁子线程开销大,因此每次在业务代码中重新构造一个子线程的方式并不常见,更常见的方式是将线程池化(线程池),由线程池的调度策略决定线程们如何执行提交给池中的任务,避免了重复构造与销毁线程的开销。上面我们提到,InheritableThreadLocal可以解决父线程向子线程传递线程私有数据的问题,但一旦子线程池化之后,InheritableThreadLocal也将不再起作用, 在局限性一解决方案原理中已经阐述:子线程的ThreadLocalMap数据是在创建线程的那一刻从父线程中"拷贝"而来,此后再也没有促使其变化的地方,而子线程由于池化复用的缘故,ThreadLocalMap一直持有的是线程创建时刻的数据,此后无论进行多少次方法调用,在(池化)子线程中通过tl.get()取出来的永远是第一次传递的数据
3.2 实现原理
仔细推敲琢磨一下,我们需要的并不是创建线程的那一刻父线程的ThreadLocal值,而是提交任务时父线程的ThreadLocal值。或者换种表述方式,需要把任务提交给线程池时的ThreadLocal值传递到任务执行时
具体思路是:父线程把任务提交给线程池时一同附上此刻自己的ThreadLocalMap,包装在task里,待线程池中某个线程执行到该任务时,用task里的ThreadLocalMap赋盖当前线程ThreadLocalMap,这样就完成了父线程向池化的子线程传递线程私有数据的目标。为了避免数据污染,待任务执行完后,线程归还回线程池之前,还需要还原ThreadLocalMap,如下示:
上面的步骤一共有6步,其中2、4、6是线程池本身的提供的能力,不需要改动,只有1、3、5有所不同,我们逐一剖析
第1步疑问:提交Task的时候如何将ThreadLocalMap一同提交上去?此处难点在于如何获取当前线程的ThreadLocalMap
ThreadLocalMap是线程私有变量,只会被ThreadLocal维护,对于外部类而言是不可见的,因此要操作ThreadLocalMap就得通过ThreadLocal(操作ThreadLocal本质上是在操作当前线程的ThreadLocalMap)
首先自定义Task,用于包装维护父线程ThreadLocalMap
public class MyTask implements Runnable {
// key是ThreadLocal,value是对应父线程的线程私有数据
private final Map<ThreadLocal<Object>, Object> threadLocalValues;
public MyTask(ThreadLocal<Object>... threadLocals) {
this.threadLocalValues = new HashMap<>(threadLocals.length);
capture(threadLocals);
}
private void capture(ThreadLocal<Object>[] threadLocals) {
for (ThreadLocal<Object> threadLocal : threadLocals) {
threadLocalValues.put(threadLocal, threadLocal.get());
}
}
@Override
public void run() {
// todo
}
}
使用时:
ThreadLocal<Object> tl1 = new ThreadLocal<>();
tl1.set("111111");
ThreadLocal<Object> tl2 = new ThreadLocal<>();
tl2.set("222222");
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 将父线程的ThreadLocal传进去,就能取到相应的值
executorService.execute(new MyTask(tl1, tl2));
tl1.remove();
tl2.remove();
如此这般,提交到线程池的MyTask就包含了父线程的ThreadLoalMap数据。我们把"拷贝"父线程TheadLocalMap的行为称为capture(拍照),一个很生动形象的词:将父线程提交任务时刻的ThreadLocal值拍个快照并保存起来,后续使用
第3步疑问:如何用父线程的ThreadLocalMap覆盖当前执行任务线程的ThreadLocalMap?
我们可以想像到,代码正执行到MyTask#run()方法,在该方法内部,能感知的上下文环境是正执行该方法的线程,以及MyTask维护的threadLocalValues(快照),除此之外,它获取不了任何外界信息–> 这称之为线程封闭
因此可以比较自然地推理出,是要用MyTask的threadLocalValues(快照)去覆盖当前线程的ThreadLocalMap。我们称这个动作为replay(重放)
public class MyTask implements Runnable {
// ...(省略)
@Override
public void run() {
replay();
// do biz
}
// 重放,用快照去覆盖当前线程的ThreadLocalMap
private void replay() {
for (Map.Entry<ThreadLocal<Object>, Object> entry : threadLocalValues.entrySet()) {
ThreadLocal<Object> threadLocal = entry.getKey();
threadLocal.set(entry.getValue());
}
}
}
第5步疑问:如何把当前线程的ThreadLocalMap还原?
Task业务逻辑执行完之后,毫无疑问需要将ThreadLocalMap还原,否则可能产生数据污染的风险
能够还原的前提是,用快照去覆盖当前线程的ThreadLocalMap之前,先将当前的ThreadLocal值保存起来,因此,修改代码如下:
private Object replay() {
// 保存当前的ThreadLocal值
Map<ThreadLocal<Object>, Object> backup = new HashMap<>();
for (ThreadLocal<Object> threadLocal : threadLocalValues.keySet()) {
backup.put(threadLocal, threadLocal.get());
}
for (Map.Entry<ThreadLocal<Object>, Object> entry : threadLocalValues.entrySet()) {
ThreadLocal<Object> threadLocal = entry.getKey();
threadLocal.set(entry.getValue());
}
return backup;
}
业务代码执行完之后,进行restore(还原):
public class MyTask implements Runnable {
// ...(省略)
@Override
public void run() {
Object backup = replay();
try {
// do biz
} finally {
restore(backup);
}
}
// 还原
private void restore(Object obj) {
Map<ThreadLocal<Object>, Object> backup = (Map<ThreadLocal<Object>, Object>) obj;
for (Map.Entry<ThreadLocal<Object>, Object> entry : backup.entrySet()) {
ThreadLocal<Object> threadLocal = entry.getKey();
threadLocal.set(entry.getValue());
}
}
}
经过上面的1、3、5步分析,我们已经把所有关键问题都分析完毕,因此,做一个小实验看看结果,测试代码:
ExecutorService executorService = Executors.newFixedThreadPool(1);
ThreadLocal<Object> tl1 = new ThreadLocal<>();
ThreadLocal<Object> tl2 = new ThreadLocal<>();
// ------------第一次调用 start -------------------
tl1.set("1111");
tl2.set("2222");
executorService.execute(new MyTask(tl1, tl2));
tl1.remove();
tl2.remove();
// ------------第一次调用 end -------------------
// ------------第二次调用 start -------------------
tl1.set("3333");
tl2.set("4444");
executorService.execute(new MyTask(tl1, tl2));
tl1.remove();
tl2.remove();
// ------------第二次调用 end -------------------
public void run() {
Object backup = replay();
try {
// do biz
doBiz();
} finally {
restore(backup);
}
}
private void doBiz() {
// 打印父线程提交任务时的ThreadLocal值
Set<ThreadLocal<Object>> threadLocals = threadLocalValues.keySet();
for (ThreadLocal<Object> threadLocal : threadLocals) {
System.out.println(threadLocal.get());
}
}
证明是可行的 但是这样还不够,稍稍有些经验的朋友应该能感受到,上面的写法仅是达到了"可用"的程度,离"好用"还有一段距离:业务在向线程池提交任务的时候,需要每次都构建自定义的Task,并将ThreadLocal的引用传入,而且Task糅进了TheadLocal管理的逻辑,这样其实形成了"业务侵入性",没有做到与业务解耦,这样的代码是不可维护的
TheadLocal管理的逻辑,业务代码不应该关心,因此为了与业务解耦,容易想到的一种解决方案是:代理。通过代理可以实现对被代理类的逻辑增强,并将通用的非业务逻辑与业务代码隔离开来(设计模式萦绕心头)
提及代理,熟悉的同学对于套路了然于心:定义代理类,实现与被代理类相同的接口,并在内部维护被代理类的实例,之后就可以对被代理的方法实现额外逻辑,来增强被代理类,代码变更后如下:
public class MyTask implements Runnable {
private Runnable task;
private final Map<ThreadLocal<Object>, Object> threadLocalValues;
public MyTask(Runnable task, ThreadLocal<Object>... threadLocals) {
this.task = task;
this.threadLocalValues = new HashMap<>(threadLocals.length);
capture(threadLocals);
}
// ...(省略)
@Override
public void run() {
Object backup = replay(); // 增强的逻辑
try {
task.run(); // 执行业务代码
} finally {
restore(backup); // 增强的逻辑
}
}
}
使用方式
executorService.execute(new MyTask(() -> {
// do biz
}, tl1, tl2));
这样提交任务时,业务开发只需要关心()->{// do biz} 业务逻辑,而不需要关心MyTask代理类如何实现的增强逻辑,做到了与业务代码的解耦
但是,上面的代码书写起来仍然有些别扭:需要业务方主动传递tl1、tl2,说明业务方仍然需要有一定程度的参与,那能不能更彻底一些,连传递tl1、tl2的行为都省去呢?答案是肯定的
如果要避免父线程"主动"传递ThreadLocal的行为,那么就必须要知道父线程往ThreadLocalMap放数据这件事,并且在事件发生的时候将ThreadLocal引用保存下来;同时,如果父线程调用ThreadLocal#remove方法清除数据,也需要将保存下来的ThreadLocal引用一同清除掉
因此,需要自定义ThreadLocal类:
public class MyThreadLocal<T> extends ThreadLocal<T> {
private static MyThreadLocal<HashSet<MyThreadLocal<Object>>> holder =
new MyThreadLocal<HashSet<MyThreadLocal<Object>>>() {
@Override
protected HashSet<MyThreadLocal<Object>> initialValue() {
return new HashSet<>();
}
};
// 重写set方法
@Override
public void set(T value) {
super.set(value);
addThisToHolder();
}
// 将ThreadLocal引用记录下来
private void addThisToHolder() {
if (!holder.get().contains(this)) {
holder.get().add((MyThreadLocal<Object>) this);
}
}
// 重写remove方法
@Override
public void remove() {
super.remove();
removeThisFromHolder();
}
// 将ThreadLocal引用记录移除
private void removeThisFromHolder() {
holder.get().remove(this);
}
}
重写set方法,在ThreadLocal#set方法执行时将ThreadLocal引用记录下来,保存在类成员变量holder中;重写remove方法,在ThreadLocal#remove方法执行时将ThreadLocal引用一并移除
接下来,父线程向线程池提交Task,不再传递ThreadLocal的引用,那又怎么完成capture的动作呢?(如果看官们忘记了,请往上翻,前文在执行capture方法时,入参是ThreadLocal引用数组)
既然我们将ThreadLocal的引用保存在MyThreadLocal#holder这个静态变量中,那我们想办法暴露holder,不就可以得到capture需要的入参了吗?
这种思路诚然是可行的,但从面向对象设计角度而言却不是最优的:holder是MyThreadLocal的静态成员变量,维护的数据是ThreadLocal集合,它不应该将自身数据暴露出去,而是遵循高内聚的设计原则,提供数据操作的能力(方法),例如提供capture的能力,但操作本身需要维护在类内部。因此应该是MyThreadLocal提供capture的能力,然后由需求方(MyTask)进行调用。代码如下
public class MyThreadLocal<T> extends ThreadLocal<T> {
// ...(省略)
public static class DataTransmit {
public static Map<ThreadLocal<Object>, Object> capture() {
Map<ThreadLocal<Object>, Object> threadLocalValues = new HashMap<>();
for (MyThreadLocal<Object> threadLocal : holder.get()) {
threadLocalValues.put(threadLocal, threadLocal.get());
}
return threadLocalValues;
}
}
}
此处,我在MyThreadLocal中定义了一个内部类DataTransmit,用于ThreadLocal的数据传输,与MyThreadLocal本身提供的能力相隔离(SRP原则)。然后,将原先定义于MyTask的capture方法搬到了DataTransmit类内,提供capture的能力。此时,MyTask构造函数代码如下:
public MyTask(Runnable task) {
this.task = task;
threadLocalValues = MyThreadLocal.DataTransmit.capture();
}
我们将capture方法搬走之后,仍然还有replay、restore方法,仔细思考,它们都是对ThreadLocal的操作,放在MyThreadLocal.DataTransmit中更合适一些,使得内聚度更高
最终,MyThreadLocal代码如下
public class MyThreadLocal<T> extends ThreadLocal<T> {
private static MyThreadLocal<HashSet<MyThreadLocal<Object>>> holder =
new MyThreadLocal<HashSet<MyThreadLocal<Object>>>() {
@Override
protected HashSet<MyThreadLocal<Object>> initialValue() {
return new HashSet<>();
}
};
@Override
public void set(T value) {
super.set(value);
addThisToHolder();
}
private void addThisToHolder() {
if (!holder.get().contains(this)) {
holder.get().add((MyThreadLocal<Object>) this);
}
}
@Override
public void remove() {
super.remove();
removeThisFromHolder();
}
private void removeThisFromHolder() {
holder.get().remove(this);
}
public static class DataTransmit {
public static Map<ThreadLocal<Object>, Object> capture() {
Map<ThreadLocal<Object>, Object> threadLocalValues = new HashMap<>();
for (MyThreadLocal<Object> threadLocal : holder.get()) {
threadLocalValues.put(threadLocal, threadLocal.get());
}
return threadLocalValues;
}
// 重放,用快照去覆盖当前线程的ThreadLocalMap
public static Object replay(Object obj) {
Map<ThreadLocal<Object>, Object> threadLocalValues = (Map<ThreadLocal<Object>, Object>)obj;
Map<ThreadLocal<Object>, Object> backup = new HashMap<>();
for (ThreadLocal<Object> threadLocal : threadLocalValues.keySet()) {
backup.put(threadLocal, threadLocal.get());
}
for (Map.Entry<ThreadLocal<Object>, Object> entry : threadLocalValues.entrySet()) {
ThreadLocal<Object> threadLocal = entry.getKey();
threadLocal.set(entry.getValue());
}
return backup;
}
public static void restore(Object obj) {
Map<ThreadLocal<Object>, Object> backup = (Map<ThreadLocal<Object>, Object>) obj;
for (Map.Entry<ThreadLocal<Object>, Object> entry : backup.entrySet()) {
ThreadLocal<Object> threadLocal = entry.getKey();
threadLocal.set(entry.getValue());
}
}
}
}
MyTask经过精简后的代码如下:
public class MyTask implements Runnable {
private Runnable task;
private final Map<ThreadLocal<Object>, Object> threadLocalValues;
public MyTask(Runnable task) {
this.task = task;
threadLocalValues = MyThreadLocal.DataTransmit.capture();
}
@Override
public void run() {
Object backup = MyThreadLocal.DataTransmit.replay(threadLocalValues);
try {
task.run();
} finally {
MyThreadLocal.DataTransmit.restore(backup);
}
}
}
使用代码如下:
ExecutorService executorService = Executors.newFixedThreadPool(1);
ThreadLocal<Object> tl1 = new MyThreadLocal<>();
ThreadLocal<Object> tl2 = new MyThreadLocal<>();
// ------------第一次调用 start -------------------
tl1.set("1111");
tl2.set("2222");
executorService.execute(new MyTask(() -> {
// do biz
System.out.println(tl1.get());
System.out.println(tl2.get());
}));
tl1.remove();
tl2.remove();
// ------------第一次调用 end -------------------
// ------------第二次调用 start -------------------
tl1.set("3333");
tl2.set("4444");
executorService.execute(new MyTask(() -> {
// do biz
System.out.println(tl1.get());
System.out.println(tl2.get());
}));
tl1.remove();
tl2.remove();
// ------------第二次调用 end -------------------
这样业务代码就简洁了。 如果还是觉得上述使用姿势有点麻烦:每次提交任务,都要构造一个MyTask,能不能连这一步都省去,变成跟规常的写法一致呢?
executorService.execute(() -> {
// do biz
System.out.println(tl1.get());
System.out.println(tl2.get());
});
答案是肯定的,首先需要明确的一点是,要增强就意味着要代理;接着稍稍转变一下思路:既然不能对Task进行代理,那么我们对线程池进行代理增强,是不是也可以达到同样的效果?
代理套路相信大家很熟悉了:
public class MyExecutorService implements ExecutorService {
private ExecutorService executorService;
public MyExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}
@Override
public void execute(Runnable command) {
executorService.execute(new MyTask(command)); // 在内部进行Task的代理
}
// ...(省略)
}
使用:
ExecutorService executorService = new MyExecutorService(Executors.newFixedThreadPool(1));
这样,业务代码又精简了一步:只需要对线程池进行代理一次即可,后续提交任务不需要手动构建MyTask
最后,如果连对线程池的代理都感觉稍显麻烦,只想使用原生的姿势,那就要请出尚方宝剑:Java Agent,在应用启动之初对JDK代码进行修改,植入代理逻辑,如此业务代码不需要进行其它改动,就享有增强后的ExecutorService以及Task
3.3 alibaba TTL 实现机制
public final void set(T value) {
if (!disableIgnoreNullValueSemantics && null == value) {
// may set null to remove value
remove();
} else {
super.set(value);
addThisToHolder();
}
}
// 在threadLocal.set() 的时候, 将ThreadLocal存放在这个变量中, 便于后面的重放和恢复
private static final InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder =
new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
return new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
}
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
}
};
@SuppressWarnings("unchecked")
private void addThisToHolder() {
if (!holder.get().containsKey(this)) {
holder.get().put((TransmittableThreadLocal<Object>) this, null); // WeakHashMap supports null value.
}
}
// ExecutorServiceTtlWrapper: 对线程池进行包装
public Future<?> submit(@NonNull Runnable task) {
return executorService.submit(TtlRunnable.get(task, false, idempotent));
}
// 对Runable 进行包装
public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
if (null == runnable) return null;
if (runnable instanceof TtlEnhanced) {
// avoid redundant decoration, and ensure idempotency
if (idempotent) return (TtlRunnable) runnable;
else throw new IllegalStateException("Already TtlRunnable!");
}
return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
}
// 类似于我们面对Task的包装方式
private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
// 捕获的数据
this.capturedRef = new AtomicReference<Object>(capture());
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
/**
* wrap method {@link Runnable#run()}.
*/
@Override
public void run() {
// 获取捕获的数据
final Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
// 进行重放数据
final Object backup = replay(captured);
try {
// 执行业务方法
runnable.run();
} finally {
// 重置ThreadLocal
restore(backup);
}
}