本文分为两部分,上部分为使用方式,下部分为解析
// 大家都知道在Tl(ThreadLocal)中 数据可以在Thread中共享
// 那么以下场景会存在问题
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
threadLocal.set(1);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(threadLocal.get()); //输出null 因为开启了新的线程
}
}).start();
// 这时使用InheritableThreadLocal (ThreadLocal的子类) 可以解决上述问题
ThreadLocal<Integer> InheritableThreadLocal = new InheritableThreadLocal<>();
InheritableThreadLocal.set(1);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(InheritableThreadLocal.get()); //输出1
}
}).start();
// 但是我们实际使用中,不可能每次使用都new Thread,一般都是使用线程池,这时就会存在新的问题
ThreadLocal<Integer> InheritableThreadLocal2 = new InheritableThreadLocal<>();
// 线程池size设置为1是为了模拟线程池中的线程都已经被初始化过
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,1, 1L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));
threadPoolExecutor.prestartAllCoreThreads(); //预先启动所有核心线程(模拟线程池中的线程都已经被初始化过,这时候又有任务提交过来)
InheritableThreadLocal2.set(2); // 为inInheritableThreadLocal设置值
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(InheritableThreadLocal2.get()); //输出null
}
TTL为阿里开源框架。
// 如何解决上面的问题,可以使用TTL TransmittableThreadLocal(可传递线程本地变量) 继承自InheritableThreadLocal,所以他拥有继承自InheritableThreadLocal的所有特性
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,1, 1L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));
threadPoolExecutor.prestartAllCoreThreads();//预先启动所有核心线程(模拟线程池中的线程都已经被初始化过,这时候又有任务提交过来)
TransmittableThreadLocal<Integer> transmittableThreadLocal = new TransmittableThreadLocal<Integer>();
transmittableThreadLocal.set(2);
// 注意。这里使用了TtlRunnable, TransmittableThreadLocal一定要搭配TTlRunnable或者TtlCallable
threadPoolExecutor.execute(TtlRunnable.get(new Runnable() {
@Override
public void run() {
System.out.println(transmittableThreadLocal.get()); //输出2
}
}));
前置了解:
//在ThreadLocal中,存在一个ThreadLocalMap.
ThreadLocal.ThreadLocalMap threadLocals = null;
// ThreadLocalMap 的组成也非常简单,我们重点关注两个地方 一个是内部类Entry
// 这个Entry是弱引用的,可以理解为: 如果这个Entry只在传入的ThreadLocal中引用它,那么会被回收。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//另一个关注点是ThreadLocalMap的参数一个数组Entry
// 它是用来干啥的呢,通过ThreadLocal 的hash 取模size 计算出来数组的下标,存入对应的ThreadLocal及Threadlocal的值
// 简单来说就是你在调用ThreadLcaol.set时,会把ThreadLocal和对应的值转换为一个Entry,放到数组中。
private Entry[] table;
set方法:
//ThreadLocal的set方法
public void set(T value) {
Thread t = Thread.currentThread(); //获取当前线程
//线程中维护了一个ThreadLocalMap ,这个getMap方法非常的简单。直接就是 return t.threadLocals;
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); //如果线程中已经存在ThreadLocalMap,转换成Entity放到对应的数组中(上面的Entity数组)
else
createMap(t, value); //线程中不存在ThreadLocalMap,为线程创建一个ThreadLocalMap,并把当前ThreadLocal和值设置为first
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get方法:
// ThreadLocal get方法
public T get() {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //获取线程ThreaLocalMap
if (map != null) {
// 通过当前的ThreadLocal的hashCode与线程中entry数组size取模计算出来数组下标,拿到对应Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); // ThreadLocal是可以直接设置默认值的,如果没有ThreadLocalMap(没有调用过set方法) 会直接返回默认值(默认null,可以重写initialValue方法))
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
以上解释了为什么ThreadLocal可以在线程中传递,因为他在线程中维护了一个ThreadLocalMap
//重点!其实Thread中维护了两个ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals = null; //Threadlocal使用
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; //InheritableThreadLocal使用
// 这个是Thread的init方法,在new一个线程的时候,会调用init方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// 我们重点关注这两段代码,获取当前线程(其实就是获取被new线程的父线程)
Thread parent = currentThread();
// 如果父线程拥有inheritableThreadLocals值,会把他设置到子线程的inheritableThreadLocals中
if (inheritThreadLocals && parent.inheritableThreadLocals != null){
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
}
这时,子线程也拥有了通过inheritableThreadLocal中设置的变量。但是是维护到inheritableThreadLocals(线程中的ThreadLocalMap)并不是维护到threadLocals(线程中的ThreadLocalMap)中
接下来我们看一下inheritThreadLocal获取数据的过程及它的特性;
get方法:
//与ThreadLocal的get方法一样的。
public T get() {
Thread t = Thread.currentThread();
// 区别点这边getMap就不在是获取t.threadLocals了。而是重写了getMap获取的inheritableThreadLocals
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
//区别点
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
set方法:
//同样的跟ThreadLocal的set一样。只是重写了几个方法
public void set(T value) {
Thread t = Thread.currentThread();
// 跟get一样获取的是inheritableThreadLocals
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 这个方法也被重写。创建的就是inheritableThreadLocals 不在是threadLocals (这俩都是ThreadLocalMap)
createMap(t, value);
}
// 区别点
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
以上就可以解释为何inheritableThreadLocal可以在子线程中传递,因为init线程的时候就会把数据存入单独的一个ThreadLocalMap中
但是inheritableThreadLocal存在问题,即只能在创建线程的时候调用init方法才可以拿到数据,如果是线程池场景,线程早就被创建好了。拿不到inheritableThreadLocal设置的值,这个时候可以使用TTL
先从TransmittableThreadLocal入手,它是一个继承了inheritableThreadLocal的类,所以它天然就可以在子线程中传递(在线程创建之前),接下来是它的一些特性:
// 全文重点!!!
// TransmittableThreadLocal 中维护了一个holder,注意是static类型的InheritableThreadLocal!
// 本质上数据还是维护在当前线路的ThreadLocalMap.Entry中
// 会把所有调用过set方法的TransmittableThreadLocal放到map里面
// 使用的是WeakHashMap
private static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder =
new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
@Override
protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
return new WeakHashMap<TransmittableThreadLocal<?>, Object>();
}
@Override
protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) {
return new WeakHashMap<TransmittableThreadLocal<?>, Object>(parentValue);
}
};
// TransmittableThreadLocal 的set方法
public final void set(T value) {
super.set(value);
if (null == value) { // may set null to remove value
removeValue();
} else {
// 特殊一点的是这个addValue
// 会往hodler的map中插入当前的TransmittableThreadLocal作为key
addValue();
}
}
private void addValue() {
if (!holder.get().containsKey(this)) {
holder.get().put(this, null);
}
}
接下来我们看看与TransmittableThreadLocal搭配使用的TTLRunnable
// TTLRunnable中的两个参数
// 在创建TTLRunable的时候,会把所有TransmittableThreadLocal的数据保存在copiedRef中
private final AtomicReference<Object> copiedRef;
//包装runnable
private final Runnable runnable;
//重点关注构造方法与装饰的run方法(私有构造, 使用TtlRunnable.get()方法进行创建实例)
private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
// 1. Transmitter.capture方法 通过TransmittableThreadLocal上维护的holder获取当前线程(父线程)所有的TransmittableThreadLocal与数据
// 2. 返回的是一个Map<TransmittableThreadLocal<?>, Object>
// 3. key为所有设置的TransmittableThreadLocal,value为TransmittableThreadLocal对应设置的value
// 4. 这里的操作都是在父线程中进行的,所以获取的值也是在父线程中Set的值。如果在子线程中set成了其它值(这时就会放到子线程的ThreadLocalMap中)。是不会影响到其它线程的数据的
// 简单来说就是获取所有TransmittableThreadLocal并把他们的值放到map中返回
this.copiedRef = new AtomicReference<Object>(Transmitter.capture());
this.runnable = runnable;
}
//包装了run方法
public void run() {
// 获取创建TTLRunable时设置的copieRef
Object copied = copiedRef.get();
if (copied == null || releaseTtlValueReferenceAfterRun && !copiedRef.compareAndSet(copied, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
// Transmitter.replay这里做了几个事情
// 1.备份一下现在holder中的所有数据(就是返回的backUp)
// 2.将copiedRef(父线程中获取到的数据)与当前holder中的数据进行对比,如果holder中有多余的数据,则remove掉
// 3.将copiedRef中的数据放到当前线程的ThreadLocalMap中(存的是TransmittableThreadLocal,然后调用了TransmittableThreadLocal的set方法,就是ThreadLocal的Set,这里要注意,因为TransmittableThreadLocal本质是一个InheritableThreadLocal所以会放到名字为inheritableThreadLocals的ThreadLocalMap中)
Object backup = Transmitter.replay(copied);
try {
// 这时。在线程中就可以直接通过TTL获取到对应数据,
runnable.run();
} finally {
// 恢复之前备份的数据,这时可能在run时又设置了新的TransmittableThreadLocal到holder中,对backup进行对比,将holder中多余的数据删除
// 把bacuUp恢复到threadLocalMap中
Transmitter.restore(backup);
}
}
//这个方法就是在创建TTLRunable时调用的Transmitter.capture
public static Object capture() {
Map<TransmittableThreadLocal<?>, Object> captured = new HashMap<TransmittableThreadLocal<?>, Object>();
for (TransmittableThreadLocal<?> threadLocal : holder.get().keySet()) {
captured.put(threadLocal, threadLocal.copyValue());
}
return captured;
}
需要注意的是。 在创建TTLRunnable时,是在父线程中创建的,但是run方法是子线程执行的。
其中,多个子线程混用TransmittableThreadLocal也不会存在问题,因为是线程隔离的,并且数据都是来源于父线程。