最后
小编精心为大家准备了一手资料
以上Java高级架构资料、源码、笔记、视频。Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术
【附】架构书籍
- BAT面试的20道高频数据库问题解析
- Java面试宝典
- Netty实战
- 算法
BATJ面试要点及Java架构师进阶资料
- TransmittableThreadLocal(TTL):(https://github.com/alibaba/transmittable-thread-local)
结合上文所述,首先我会将TTL项目相关文档、issues列表认真阅读一遍,让自己对项目能有个大体的认识,并梳理出项目一些关键信息,比如:
-
文档(https://github.com/alibaba/transmittable-thread-local#-%E5%8A%9F%E8%83%BD)
-
issues列表(https://github.com/alibaba/transmittable-thread-local/issues)
▐ 核心要解决的问题
用于解决「在线程池或线程会被复用情况下,如何解决线程ThreadLocal传值问题」
▐ 有哪些典型业务场景
-
- 分布式跟踪系统或全链路压测(即链路打标)
-
日志收集记录系统上下文
-
Session级Cache
-
应用容器或上层框架跨应用代码给下层SDK传递信息
▐ 使用到的技术
有线程、线程池、ThreadLocal、InheritableThreadLocal、并发、线程安全等。
然后,再结合使用文档编写几个测试demo,通过程序代码练习和框架使用,一步步加深对框架的理解。比如我这里首先会拿TTL与原生JDK InheritableThreadLocal进行不同比较,体验两者的核心区别。
public class ThreadLocalTest {
private static final AtomicInteger ID_SEQ = new AtomicInteger();
private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(1, r -> new Thread(r, “TTL-TEST-” + ID_SEQ.getAndIncrement()));
//
private static ThreadLocal THREAD_LOCAL = new InheritableThreadLocal<>();
//⑴ 声明TransmittableThreadLocal类型的ThreadLocal
//private static ThreadLocal THREAD_LOCAL = new TransmittableThreadLocal<>();
public static void testThreadLocal() throws InterruptedException {
try {
//doSomething()…
THREAD_LOCAL.set(“set-task-init-value”);
//
Runnable task1 = () -> {
try {
String manTaskCtx = THREAD_LOCAL.get();
System.out.println(“task1:” + Thread.currentThread() + “, get ctx:” + manTaskCtx);
THREAD_LOCAL.set(“task1-set-value”);
} finally {
THREAD_LOCAL.remove();
}
};
EXECUTOR.submit(task1);
//doSomething…
TimeUnit.SECONDS.sleep(3);
//⑵ 设置期望task2可获取的上下文
THREAD_LOCAL.set(“main-task-value”);
//⑶ task2的异步任务逻辑中期望获取⑵中的上下文
Runnable task2 = () -> {
String manTaskCtx = THREAD_LOCAL.get();
System.out.println(“task2:” + Thread.currentThread() + “, get ctx :” + manTaskCtx);
};
//⑷ 转换为TransmittableThreadLocal 增强的Runnable
//task2 = TtlRunnable.get(task2);
EXECUTOR.submit(task2);
}finally {
THREAD_LOCAL.remove();
}
}
public static void main(String[] args) throws InterruptedException {
testThreadLocal();
}
}
//InheritableThreadLocal 运行结果:
task1:Thread[TTL-TEST-0,5,main], get ctx:set-task-init-value
task2:Thread[TTL-TEST-0,5,main], get ctx :null
//TransmittableThreadLocal 运行结果
task1:Thread[TTL-TEST-0,5,main], get ctx:set-task-init-value
task2:Thread[TTL-TEST-0,5,main], get ctx :main-task-value
通过代码运行结果,我们可以直观看到使用JDK原生InheritableThreadLocal,在task2异步任务中是无法正确获取代码⑵处所设置的上下文参数,只有改用TransmittableThreadLocal之后,程序才如我们预期正常获取。
不难发现,由JDK原生ThreadLocal切换到TransmittableThreadLocal,只需要做极少量的代码适配即可。
//private static ThreadLocal THREAD_LOCAL = new InheritableThreadLocal<>();
//⑴ 声明TransmittableThreadLocal类型的ThreadLocal
private static ThreadLocal THREAD_LOCAL = new TransmittableThreadLocal<>();
…
//⑷ 转换为TransmittableThreadLocal 增强的Runnable
task2 = TtlRunnable.get(task2);
相信看到这里我们都会不禁想问,为什么只需要简单的更改两行代码,就可以平滑实现上下文透传?TTL框架背后具体都做了哪些工作,到底是怎么实现的呢?相信你和我一样都会比较好奇,也一定有想立马阅读源码一探究竟的冲动。
不过,通常这个时候,我并不会一头扎进源码,一般都会先做几项准备工作,一是回到设计文档再仔细的阅读下相关实现方案,把关键流程和原理了解清楚;二是把涉及到的技术体相关的基础知识再复习或学习一遍,以避免由于一些基础知识原理的不了解,导致源码无法深入研究或花费大量精力。像这里如果我对Thread、ThreadLocal、InheritableThreadLocal、线程池等相关知识不熟悉的话,一定会把相关知识先学习一遍,比如ThreadLocal基本原理、底层数据结构、InheritableThreadLocal如何实现父子线程传递等等。
假设这里你对这些知识都已掌握,如果不熟悉,网上相关介绍文章也早已是汗牛充栋,你搜索学习下即可。这里我们先带着到底如何实现的这个疑问,一起来探究下核心源码实现。
首先把源码clone下来导入IDE,然后结合文档把系统工程结构和各功能模块职责快速熟悉一遍,然后结合文档和Demo找到关键接口和实现类,利用IDE把相关类图结构生成出来,以便快速理解类之间关系。非常不错,TTL整体代码非常精练、命名和包信息描述也都非常规范和清晰,我们可以快速圈出来。
从类图中我们可以清晰看到核心关键类TransmittableThreadLocal是从ThreadLocal继承而来,这样的好处是不破坏ThreadLocal原生能力的同时还可增强和扩展自有能力,也可保证业务代码原有互操作性和最小改动。
然后结合Demo代码,我们不难发现使用TTL主要有三个步骤,TransmittableThreadLocal声明、set、remove方法的调用。根据整个使用流程和方法调用栈,我们也可以很方便梳理出整个代码处理初始化、调用时序。
(这里借用官方原图)
通过流程图,我们可以清晰看到TTL核心流程和原理是通过TransmittableThreadLocal.Transmitter 抓取当前线程的所有TTL值并在其他线程进行回放,然后在回放线程执行完业务操作后,再恢复为回放线程原来的TTL值。
TransmittableThreadLocal.Transmitter提供了所有TTL值的抓取、回放和恢复方法(即CRR操作):
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;
最后
看完上述知识点如果你深感Java基础不够扎实,或者刷题刷的不够、知识不全面
小编专门为你量身定制了一套<Java一线大厂高岗面试题解析合集:JAVA基础-中级-高级面试+SSM框架+分布式+性能调优+微服务+并发编程+网络+设计模式+数据结构与算法>
针对知识面不够,也莫慌!还有一整套的<Java核心进阶手册>,可以瞬间查漏补缺
全都是一丢一丢的收集整理纯手打出来的
更有纯手绘的各大知识体系大纲,可供梳理:Java筑基、MySQL、Redis、并发编程、Spring、分布式高性能架构知识、微服务架构知识、开源框架知识点等等的xmind手绘图~
最后
看完上述知识点如果你深感Java基础不够扎实,或者刷题刷的不够、知识不全面
小编专门为你量身定制了一套<Java一线大厂高岗面试题解析合集:JAVA基础-中级-高级面试+SSM框架+分布式+性能调优+微服务+并发编程+网络+设计模式+数据结构与算法>
[外链图片转存中…(img-pkY7sk1L-1715344464402)]
针对知识面不够,也莫慌!还有一整套的<Java核心进阶手册>,可以瞬间查漏补缺
[外链图片转存中…(img-EDo1egcL-1715344464402)]
全都是一丢一丢的收集整理纯手打出来的
更有纯手绘的各大知识体系大纲,可供梳理:Java筑基、MySQL、Redis、并发编程、Spring、分布式高性能架构知识、微服务架构知识、开源框架知识点等等的xmind手绘图~
[外链图片转存中…(img-knUnNaY2-1715344464403)]
[外链图片转存中…(img-OUQHZKM0-1715344464403)]