要解决什么问题
JDK
的InheritableThreadLocal
类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal
值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal
值传递到 任务执行时。
在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal
值的传递功能
关于InheritableThreadLocal
放在哪里
每个线程内部有这么一个属性
/*
* 与此线程有关的InheritableThreadLocal值
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = 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;
}
}
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
...
}
何时初始化的
父线程第一次调用set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
子线程初始化的时候
java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long, java.security.AccessControlContext, boolean)
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
何时赋值的
父线程调用set
子线程初始化
TransmittableThreadLocal
本质
本质上是一个InheritableThreadLocal
public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> implements TtlCopier<T>
存放在线程内部的inheritableThreadLocals,不涉及线程池时可以当成inherateAbleThreadLocal使用
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
值保存在哪里
重写get和set
然后重写了get和set方法,这里是比较特别的地方是有一个addThisToHolder(),见名知意。
/**
* see {@link InheritableThreadLocal#get()}
*/
@Override
public final T get() {
T value = super.get();
if (disableIgnoreNullValueSemantics || null != value) addThisToHolder();
return value;
}
/**
* see {@link InheritableThreadLocal#set}
*/
@Override
public final void set(T value) {
if (!disableIgnoreNullValueSemantics && null == value) {
// may set null to remove value
remove();
} else {
super.set(value);
addThisToHolder();
}
}
private void addThisToHolder() {
if (!holder.get().containsKey(this)) {
holder.get().put((TransmittableThreadLocal<Object>) this, null); // WeakHashMap supports null value.
}
}
TransmittableThreadLocal核心属性 #holder
// 1. holder 本质上是一个 InheritableThreadLocal(a *ThreadLocal*).
// 2. 内部的值的类型是 WeakHashMap<TransmittableThreadLocal<Object>, ?>.
// 2.1 这个 WeakHashMap 被当成一个 *Set* 使用:
// 这个WeakHashMap的value永远是null, 不会被使用到.
// 2.2 WeakHashMap 支持 *null* 作为value.
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);
}
};
holder的作用(存放的是所有和当前线程产生关联的 TransmittableThreadLocal)
当线程首次调用TransmittableThreadLocal<Object>类型变量 A 的get或者set方法,实际上做了两个动作,在现成的
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
中插入两个值
- key为TransmittableThreadLocal<Object>的Entry
- key为InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, null>>的Entry(注意这个entry当Set使用的)
也就是说,任何一个当前线程只要调用任意一个TransmittableThreadLocal类型的变量的get或set方法,那么这个TransmittableThreadLocal变量就会被holder偷偷记录下来。
当我们使用TransmittableThreadLocal时, 对线程意味着什么
线程会会持有所有自己使用过的TransmittableThreadLocal
的引用。
线程的inheritableThreadLocals
属性会有一个被holder创建的WeakHashMap<TransmittableThreadLocal<Object>, null>
,里面记录了所有和当前线程有过关联的TransmittableThreadLocal
值如何传递
-
修饰
Runnable
和Callable
-
修饰线程池
我们来写话应该怎么做?
一句话:把提交任务线程的TransmittableThreadLocal
copy一份到当前线程。
盲点:copy之后能够还原线程池线程的值
以TtlRunnable作为示例分析一下
public final class TtlRunnable implements Runnable, TtlWrapper<Runnable>, TtlEnhanced, TtlAttachments
复制
主要关注一下静态工厂方法
/**
* Factory method, wrap input {@link Runnable} to {@link TtlRunnable}.
*
* @param runnable input {@link Runnable}. if input is {@code null}, return {@code null}.
* @return Wrapped {@link Runnable}
* @throws IllegalStateException when input is {@link TtlRunnable} already.
*/
@Nullable
public static TtlRunnable get(@Nullable Runnable runnable) {
return get(runnable, false, false);
}
// 调用私有构造函数
private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
this.capturedRef = new AtomicReference<Object>(capture());
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
// HashMap<ThreadLocal<Object>, Object> 和 HashMap<TransmittableThreadLocal<Object>, Object>
private final AtomicReference<Object> capturedRef;
这里涉及到了一个比较重要的变量capturedRef,本质上是一个原子对象引用。
这个引用指向的是一个快照,快照里是当前线程的TransmittableThreadLocal
值的副本
public static Object capture() {
return new Snapshot(captureTtlValues(), captureThreadLocalValues());
}
private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
//通过holder获取和当前线程有关联的`TransmittableThreadLocal`,挨个复制,然后返回
for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
ttl2Value.put(threadLocal, threadLocal.copyValue());
}
return ttl2Value;
}
到此为止,父线程的值已经全部接收了下来;
运用
被包装的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 {
// 执行完毕,将当前线程(线程池线程)的值恢复回来
restore(backup);
}
}
回放
@NonNull
public static Object replay(@NonNull Object captured) {
final Snapshot capturedSnapshot = (Snapshot) captured;
return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value));
}
@NonNull
private static HashMap<TransmittableThreadLocal<Object>, Object> replayTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> captured) {
HashMap<TransmittableThreadLocal<Object>, Object> backup = new HashMap<TransmittableThreadLocal<Object>, Object>();
//这个holder.get在当前线程没有使用过TransmittableThreadLocal时为空
for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
//能进入这里说明当前线程执行过TransmittableThreadLocal有关操作
TransmittableThreadLocal<Object> threadLocal = iterator.next();
// backup
backup.put(threadLocal, threadLocal.get());
// clear the TTL values that is not in captured
// avoid the extra TTL values after replay when run task
if (!captured.containsKey(threadLocal)) {
//如果启动线程的TransmittableThreadLocals里没有包含这个额TransmittableThreadLocal
//把当前线程的有关这个TransmittableThreadLocal的数据清空
iterator.remove();
threadLocal.superRemove();
}
}
// set TTL values to captured
setTtlValuesTo(captured);
// call beforeExecute callback
doExecuteCallback(true);
return backup;
}
private static void setTtlValuesTo(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> ttlValues) {
//对于启动线程里面的值 挨个加入到当前线程里
for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry : ttlValues.entrySet()) {
TransmittableThreadLocal<Object> threadLocal = entry.getKey();
threadLocal.set(entry.getValue());
}
}
恢复就不说了
复盘
主要依靠holder记录所有关联的TransmittableThreadLocal
通过包装的ttlRunnable或者ttlCallable,生成任务时产生一个提交线程的快照,
重播并备份,恢复,完事
简单思考
问题一
先set,然后生成ttlRunnable,再set,然后提交 ,我觉得会出问题,快照已经保存过了的(在生成任务的时候保存快照;
这一点就有趣了。但道理上也讲得通;
但是还是建议使用包装线程池的方式。
问题二
主线程启动子线程 子线程再提交一个任务到线程池,holder会不会没有和子线程关联?
holder本质上也是inheritAbleThreadLocal,会被赋值给子线程 这里间接地和holder产生了关联