TransmittableThreadLocal源碼分析

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)自己的前置和后置邏輯
  • 通過中間變量進行上層和下層的傳遞
  • 測試用例寫的非常詳盡熬拒,遇到不理解的可以通過單測來加深理解,算是我看過的非常不錯的注釋詳盡的單測代碼了??
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末垫竞,一起剝皮案震驚了整個濱河市澎粟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌欢瞪,老刑警劉巖活烙,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異遣鼓,居然都是意外死亡啸盏,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門骑祟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來回懦,“玉大人气笙,你說我怎么就攤上這事∏釉危” “怎么了潜圃?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長舟茶。 經(jīng)常有香客問我秉犹,道長,這世上最難降的妖魔是什么稚晚? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任崇堵,我火速辦了婚禮,結果婚禮上客燕,老公的妹妹穿的比我還像新娘鸳劳。我一直安慰自己,他們只是感情好也搓,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布赏廓。 她就那樣靜靜地躺著,像睡著了一般傍妒。 火紅的嫁衣襯著肌膚如雪幔摸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天颤练,我揣著相機與錄音既忆,去河邊找鬼。 笑死嗦玖,一個胖子當著我的面吹牛患雇,可吹牛的內容都是我干的。 我是一名探鬼主播宇挫,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼苛吱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了器瘪?” 一聲冷哼從身側響起翠储,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎橡疼,沒想到半個月后援所,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡衰齐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年任斋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耻涛。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡废酷,死狀恐怖,靈堂內的尸體忽然破棺而出抹缕,到底是詐尸還是另有隱情澈蟆,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布卓研,位于F島的核電站趴俘,受9級特大地震影響,放射性物質發(fā)生泄漏奏赘。R本人自食惡果不足惜寥闪,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望磨淌。 院中可真熱鬧疲憋,春花似錦、人聲如沸梁只。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搪锣。三九已至秋忙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間构舟,已是汗流浹背灰追。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留狗超,地道東北人监嗜。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像抡谐,于是被迫代替她去往敵國和親裁奇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354