capture方法:抓取线程(线程A)的所有TTL值。
replay方法:在另一个线程(线程B)中,回放在capture方法中抓取的TTL值,并返回 回放前TTL值的备份
restore方法:恢复线程B执行replay方法之前的TTL值(即备份)
弄明白核心流程和原理后,我们现在来分析下相关核心代码,在声明TransmittableThreadLocal变量时,我们会发现框架初始化了一个类级别的变量holder用于存储用户设置的所有ttl上下文,也是为了后续执行capture抓取时使用。
// Note about the holder:
// 1. holder self is a InheritableThreadLocal(a ThreadLocal).
// 2. The type of value in the holder is WeakHashMap<TransmittableThreadLocal, ?>.
// 2.1 but the WeakHashMap is used as a Set:
// the value of WeakHashMap is always null, and never used.
// 2.2 WeakHashMap support null value.
private static final InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal, ?>> holder =
new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal, ?>>() {
@Override
protected WeakHashMap<TransmittableThreadLocal, ?> initialValue() {
return new WeakHashMap<TransmittableThreadLocal, Object>();
}
@Override
protected WeakHashMap<TransmittableThreadLocal, ?> childValue(WeakHashMap<TransmittableThreadLocal, ?> parentValue) {
return new WeakHashMap<TransmittableThreadLocal, Object>(parentValue);
}
};
/**
- 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) this, null); // WeakHashMap supports null value.
}
}
结合set方法实现来看,我们会发现holder变量设计的非常巧妙,业务设置的上下文value部分继续复用ThreadLocal原有数据结构ThreadLocalMap来存储( super.set(value));capture的数据源利用holder进行引用存储(addThisToHolder put this)。这样的好处是既可保持ThreadLocal数据存储原有的封装性,又很好实现扩展。除此之外,holder还有其他设计考究,这里抛出来大家可以思考下:
-
为什么holder需要设计成static final类级别变量?
-
ttl变量的存储为什么需要使用WeakHashMap,而不是hashmap或其他?
然后我们再来看异步task转换 TtlRunnable.get(task2) 核心代码实现,代码整体实现相对比较简单,get方法是一个静态工厂方法,主要作用是将业务传入的普通Runnable task装饰成TtlRunable类,并在TtlRunable构造方法中进行线程capture动作(具体实现我们后面再分析),然后将结果存储到对象属性capturedRef中。
@Nullable
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!”);
}
//将入参runnable进行了装饰
return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
}
//…
public final class TtlRunnable implements Runnable, TtlWrapper, TtlEnhanced, TtlAttachments {
private final AtomicReference capturedRef;
private final Runnable runnable;
private final boolean releaseTtlValueReferenceAfterRun;
private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
this.capturedRef = new AtomicReference(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 {
restore(backup);
}
}
//…
}
然后是run方法,这也是核心关键的CRR操作了。这里通过模板方法将CRR操作编排在业务逻辑执行的前后了,也即业务逻辑执行前会将capturer的值进行replay恢复,执行后进行复原restore操作。同样这里也有几个问题很值我们思考:
-
capture操作为什么需要放到TtlRunnable构造方法中,而不能在run方法中?
-
代码中使用了哪两个设计模式,使用设计模式的好处是什么?
-
业务执行完之后为什么还需要restore操作?
接下来,我们再分别对capture、replay、restore方法实现做个一一分析。首先是capture方法,我们可以看到capture操作整体比较简单,主要是将set操作保存到holder变量中的值进行遍历并以Snapshot结构进行存储返回。
/**
-
Capture all {@link TransmittableThreadLocal} and registered {@link ThreadLocal} values in the current thread.
-
@return the captured {@link TransmittableThreadLocal} values
-
@since 2.3.0
*/
@NonNull
public static Object capture() {
return new Snapshot(captureTtlValues(), captureThreadLocalValues());
}
private static HashMap<TransmittableThreadLocal, Object> captureTtlValues() {
HashMap<TransmittableThreadLocal, Object> ttl2Value = new HashMap<TransmittableThreadLocal, Object>();
for (TransmittableThreadLocal threadLocal : holder.get().keySet()) {
ttl2Value.put(threadLocal, threadLocal.copyValue());
}
return ttl2Value;
}
private static HashMap<ThreadLocal, Object> captureThreadLocalValues() {
final HashMap<ThreadLocal, Object> threadLocal2Value = new HashMap<ThreadLocal, Object>();
for (Map.Entry<ThreadLocal, TtlCopier> entry : threadLocalHolder.entrySet()) {
final ThreadLocal threadLocal = entry.getKey();
final TtlCopier copier = entry.getValue();
threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get()));
}
return threadLocal2Value;
}
另一个captureThreadLocalValues,主要是用于将一些已有ThreadLocal中的上下文一起复制,已有ThreadLocal需要通过registerThreadLocal方法来单独注册。相关代码如下:
public static class Transmitter {
//…
private static volatile WeakHashMap<ThreadLocal, TtlCopier> threadLocalHolder = new WeakHashMap<ThreadLocal, TtlCopier>();
private static final Object threadLocalHolderUpdateLock = new Object();
//…
public static boolean registerThreadLocal(@NonNull ThreadLocal threadLocal, @NonNull TtlCopier copier, boolean force) {
if (threadLocal instanceof TransmittableThreadLocal) {
logger.warning(“register a TransmittableThreadLocal instance, this is unnecessary!”);
return true;
}
synchronized (threadLocalHolderUpdateLock) {
if (!force && threadLocalHolder.containsKey(threadLocal)) return false;
WeakHashMap<ThreadLocal, TtlCopier> newHolder = new WeakHashMap<ThreadLocal, TtlCopier>(threadLocalHolder);
newHolder.put((ThreadLocal) threadLocal, (TtlCopier) copier);
threadLocalHolder = newHolder;
return true;
}
}
//…
}
这里代码有个非常关键的处理,由于WeakHashMap非线程安全,为了避免并发问题安全加上了synchronized锁操作。这里有可以思考下除了synchronized关键字还有什么保障线程安全的方法。另外,实现threadLocal注册时为已经在锁块中了,为什么还要做new copy重新替换操作,这样做目的是什么?大家可以想想看。
最后就是replay和restore方法,整体实现逻辑非常清晰,主要是将captured的值在当前线程ThreadLocal中进行重新赋值初始化,以及业务执行后恢复到原来。这里很佩服作者对不同情况的细致考虑,不是直接将当前holder中的上下文直接备份,而是与之前已capture的内容比较,将业务后set的上下文进行剔除,以免在恢复restore时出现前后不一致的情况。
@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> replayTtlValues(@NonNull HashMap<TransmittableThreadLocal, Object> captured) {
HashMap<TransmittableThreadLocal, Object> backup = new HashMap<TransmittableThreadLocal, Object>();
for (final Iterator<TransmittableThreadLocal> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
TransmittableThreadLocal 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)) {
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> ttlValues) {
for (Map.Entry<TransmittableThreadLocal, Object> entry : ttlValues.entrySet()) {
TransmittableThreadLocal threadLocal = entry.getKey();
threadLocal.set(entry.getValue());
}
}
public static void restore(@NonNull Object backup) {
final Snapshot backupSnapshot = (Snapshot) backup;
restoreTtlValues(backupSnapshot.ttl2Value);
restoreThreadLocalValues(backupSnapshot.threadLocal2Value);
}
private static void restoreTtlValues(@NonNull HashMap<TransmittableThreadLocal, Object> backup) {
// call afterExecute callback
doExecuteCallback(false);
for (final Iterator<TransmittableThreadLocal> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
TransmittableThreadLocal threadLocal = iterator.next();
// clear the TTL values that is not in backup
// avoid the extra TTL values after restore
if (!backup.containsKey(threadLocal)) {
iterator.remove();
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
写在最后
为了这次面试,也收集了很多的面试题!
以下是部分面试题截图
过华为、OPPO等大厂,18年进入阿里一直到现在。**
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-gHaEI2Ng-1711106234322)]
[外链图片转存中…(img-1JkNQYJD-1711106234323)]
[外链图片转存中…(img-cgkn2UOT-1711106234324)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-h7hcWECZ-1711106234325)]
写在最后
为了这次面试,也收集了很多的面试题!
以下是部分面试题截图
[外链图片转存中…(img-gjKwY51A-1711106234325)]