1. 類繼承關系
類繼承關系
2. 時序圖
時序圖直接用作者畫的,非常詳細
TransmittableThreadLocal-sequence-diagram.png
3. 流程說明
1. createTtl 這一步最重要的就是初始化了holder
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);
}
};
holder的兩個重寫方法說明:
- initialValue調用的時機是在holder.get()的時候如果當前線程的ThreadLocalMap為空围肥,則調用這個方法初始化,見單測代碼 TtlRunnableTest.test_ttlRunnable_inSameThread
- childValue調用的時機是子線程初始化的時候 見單測代碼 TtlRunnableTest.test_ttlRunnable_asyncWithNewThread
holder可以說是ttl最核心的變量之一了浪南,理解了這個變量狀態(tài)的流轉,就理解了這個框架切端。為啥使用WeakHashMap應該是為了內存回收??
2. setTtlValue 這一步主要做了兩件事
(1)先在ttl的父類ThreadLocal設置該線程的值
@Override
public final void set(T value) {
? // 先調用ThreadLocal的set方法
super.set(value);
if (null == value) { // may set null to remove value
removeValue();
} else {
addValue();
}
}
?
//ThreadLocal 的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);
}???
(2) 往holder中添加當前的ttl
private void addValue() {
if (!holder.get().containsKey(this)) {
holder.get().put(this, null); // WeakHashMap supports null value.
// System.out.println(Thread.currentThread().getName()+" "+holder.get()); //可以加下這個輸出下加深理解
}
}
(3) 調用ThreadLocal的get方法
public T get() {
Thread t = Thread.currentThread();
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;
}
}
//調用ttl的holder重寫的initialValue方法
return setInitialValue();
}
所以有了如下的映射關系帝洪,通過當前thread獲取獲取holder中的keySet,通過遍歷keySet獲取ttl,通過ttl委托ThreadLocal維護變量的值
holder運行時的結構:
main {com.alibaba.Utils$newTtlInstanceAndPut$ttl$1@1fc2b765(parent-create-unmodified-in-child)=null}
3. createBizTaskRunnable
實現(xiàn)runnable接口, 自己的業(yè)務邏輯
4. createTtlRunnableWrapper(Runnable runnable)
這一步最重要的是把當前線程的threadLocal的值給拷貝到了capturedRef ,為啥用AtomicRef飞傀,需要原子更新操作TtlRunnable line47
4.1 TtlRunnable get(Runnable runnable)
//runnable 提交的業(yè)務runnable
//releaseTtlValueReferenceAfterRun 是否在value返回后釋放皇型,避免內存泄漏
//idempotent 是否需要冪等
public static TtlRunnable get(Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
if (null == runnable) {
return null;
}
if (runnable instanceof TtlRunnable) {
if (idempotent) {
// avoid redundant decoration, and ensure idempotency
return (TtlRunnable) runnable;
} else {
throw new IllegalStateException("Already TtlRunnable!");
}
}
return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
}
4.2 創(chuàng)建新的TtlRunnable包裝runnable
private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
//該線程包含的所有ttl的變量
//類型是 Map<TransmittableThreadLocal<?>, Object>
this.capturedRef = new AtomicReference<Object>(capture());
this.runnable = runnable;
//是否需要在使用后釋放
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
4.3 獲取當前線程所有的ttl的實例和對應的threadLocal的值
重點: capture的是父線程的ttl, 也就是創(chuàng)建TtlRunnable的線程诬烹,這個很重要很容易誤解。
public static Object capture() {
Map<TransmittableThreadLocal<?>, Object> captured = new HashMap<TransmittableThreadLocal<?>, Object>();
//holder.get().keySet()是當前線程使用的ttl
//在set這一步 holder添加了ttl的關系弃鸦,同時ttl中set了當前線程的value
for (TransmittableThreadLocal<?> threadLocal : holder.get().keySet()) {
captured.put(threadLocal, threadLocal.copyValue());
}
return captured;
}
5. submitTtlRunnableToThreadPool
提交任務給線程池
6. 線程池執(zhí)行run方法
步驟6之后的邏輯已經(jīng)和業(yè)務代碼沒有關系了
(1) TtlRunnable重寫了Runnable的run方法
@Override
public void run() {
//獲取任務提交時绞吁,即包裝runnable時的ttl
Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
//執(zhí)行前 獲取backup
Object backup = replay(captured);
try {
//執(zhí)行run方法
runnable.run();
} finally {
//通過backup恢復當前線程的ttl,避免run方法中修改了ttl
//避免線程復用造成父線程的ttl丟失
restore(backup);
}
}
(2) replay(captured)方法
- 聲明兩個局部變量 capturedMap和backup
- 遍歷holder.get().entrySet().iterator()
- 將threadLocal的值放到backup變量
- 如果capturedMap不包含holder中該線程的threadLocal的key唬格,將holder中多余的threadLocal key remove, 將其父類的threadLocal變量移除家破,避免線程運行時獲取ttl的干擾
public static Object replay(Object captured) {
@SuppressWarnings("unchecked")
Map<TransmittableThreadLocal<?>, Object> capturedMap = (Map<TransmittableThreadLocal<?>, Object>) captured;
Map<TransmittableThreadLocal<?>, Object> backup = new HashMap<TransmittableThreadLocal<?>, Object>();
for (Iterator<? extends Map.Entry<TransmittableThreadLocal<?>, ?>> iterator = holder.get().entrySet().iterator();
iterator.hasNext(); ) {
Map.Entry<TransmittableThreadLocal<?>, ?> next = iterator.next();
TransmittableThreadLocal<?> threadLocal = next.getKey();
// 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 (!capturedMap.containsKey(threadLocal)) {
iterator.remove();
threadLocal.superRemove();
}
}
// set values to captured TTL
setTtlValuesTo(capturedMap);
// call beforeExecute callback
doExecuteCallback(true);
return backup;
}
將capturedMap的值拷貝到當前線程的threadLocal颜说,并更新holder,這一步的精髓就是通過這個Map<TransmittableThreadLocal<?>, Object>類型的值將ttl從父線程往子線程中塞數(shù)據(jù)汰聋。類似的用法其實還有打破java雙親委托往threadContext中塞數(shù)據(jù)门粪,典型應用就是jdbc的驅動的實現(xiàn)
private static void setTtlValuesTo(Map<TransmittableThreadLocal<?>, Object> ttlValues) {
for (Map.Entry<TransmittableThreadLocal<?>, Object> entry : ttlValues.entrySet()) {
@SuppressWarnings("unchecked")
TransmittableThreadLocal<Object> threadLocal = (TransmittableThreadLocal<Object>) entry.getKey();
threadLocal.set(entry.getValue());
}
}
doExecuteCallback 自定義擴展邏輯,可以做前置攔截
(3) 執(zhí)行run方法
(4) 執(zhí)行后 restore(backup)
將當前線程的ttl恢復到任務提交時
- doExecuteCallback(false) 后置處理方法烹困,用戶可以自定義
- 遍歷holder.get().entrySet().iterator()
- 如果backupMap不包含holder中該線程的threadLocal的key玄妈,將holder中多余的threadLocal key remove
- setTtlValuesTo(backupMap)
4. 重點
-
理解TransmittableThreadLocal的holder變量的流轉
- holder是一個InheritableThreadLocal靜態(tài)變量, InheritableThreadLocal可以讓threadLocal的值在子線程init的時候從父線程傳遞到子線程
-
holder其實維護了兩層關系
- 當前線程本地變量和ttl的映射關系
- 任務提交時holder和所有提交時的ttl的映射關系
-
狀態(tài)流轉圖
transmittable holder.png
- 框架需要實現(xiàn)的目的是啥
- 把 任務提交給線程池時的ThreadLocal值傳遞到 任務執(zhí)行時
5. 學習的地方
- 核心代碼非常少, 王垠在博文《如何閱讀別人的代碼》說到的"造就我今天的編程能力和洞察力的,不是幾百萬行的大型項目髓梅,而是小到幾行拟蜻,幾十行之短的練習。不要小看了這些短小的代碼枯饿,它們就是編程最精髓的東西酝锅。反反復復琢磨這些短小的代碼,不斷改進和提煉里面的結構奢方,磨礪自己的思維"搔扁。 個人覺得ttl框架精髓主要是holder變量的設計和維護,使用capturedRef實現(xiàn)父子(線程池)的ttl值傳遞蟋字,深刻理解ThreadLocal和InheritableThreadLocal的繼承關系阁谆,以及Thread和ThreadLocalMap的關系。
- 代碼的注釋非常的詳細愉老,比如方法的描述场绿,參數(shù)的具體的意義,功能點是哪個版本之后才有的嫉入,當然作者的markdown的文檔寫的不錯很生動
- 用到了很多設計模式焰盗,比如模版方法模式,裝飾器模式咒林,委托模式等
- 用到了類似AOP的概念 可以實現(xiàn)自己的前置和后置邏輯
- 通過中間變量進行上層和下層的傳遞
- 測試用例寫的非常詳盡熬拒,遇到不理解的可以通過單測來加深理解,算是我看過的非常不錯的注釋詳盡的單測代碼了??