多線程篇-父子線程的上下文傳遞

前言

在實際的工作中蕉堰,有些時候我們會遇到一些線程之間共享數(shù)據(jù)的問題旅掂,比如一下幾個典型的業(yè)務場景,舉幾個例子什荣,大家能夠更加直觀的感受到

  1. 分布式鏈路追蹤系統(tǒng)

    這個不必多說矾缓,如果我們需要記錄系統(tǒng)的每個調用鏈路,那么每一個子系統(tǒng)里面稻爬,如果調用了異步線程來做處理的話嗜闻,那么類似這種鏈路是不是需要收集起來呢?

  2. 日志收集記錄系統(tǒng)上下文

    在實際的日志打印記錄中桅锄,一個http請求進來的話琉雳,每一行日志,日志產(chǎn)生的線程信息竞滓?上下文信息咐吼,是不是需要記錄下來呢?

上面我舉的是我們最最最常見的兩個例子了商佑, 做了幾年開發(fā)的都能理解為啥要有這個東西锯茄,下面我們仔細聊一下這個問題,

InheritableThreadLocal

其實說到這個問題茶没,有些同學就會想到InheritableThreadLocal 這個工具了肌幽,這是JDK給我們提供的的工具,該工具可以解決父子線程之間的值的傳遞抓半,我們先來一個簡單的demo, 然后再進行原理分析

demo

/**
 * ce
 *
 * @author zhangyunhe
 * @date 2020-04-22 16:19
 */
public class InteritableTest2 {


    static ThreadLocal<String> local = new InheritableThreadLocal<>();
    // 初始化一個長度為1 的線程池
    static ExecutorService poolExecutor = Executors.newFixedThreadPool(1);

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        InteritableTest2 test = new InteritableTest2();
        test.test();
    }
    private void test(){
        // 設置一個初始值
        local.set("天王老子");
        poolExecutor.submit(new Task());

    }

    class Task implements Runnable{

        @Override
        public void run() {
            // 子線程里面打印獲取到的值
            System.out.println(Thread.currentThread().getName()+":"+local.get());
        }
    }
}

輸出結果

pool-1-thread-1:天王老子

從上面可以看到喂急, 子線程pool-1-thread-1可以獲取到父線程在local里面設置的值,這就實現(xiàn)了值的傳遞了笛求。

源碼分析

下面我們從源碼的角度上看一下InheritableThreadLocal的實現(xiàn)廊移,他究竟是怎么做到父子線程之間線程的傳遞的糕簿。

我們首先看一下Thread創(chuàng)建的代碼。

Thread

線程初始化的代碼狡孔,可以看到重點在init方法

public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
 }
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
}
init

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
            // 1. 獲取當前線程為父線程懂诗,其實就是創(chuàng)建這個線程的線程
        Thread parent = currentThread();
                // 省略代碼。苗膝。殃恒。。辱揭。
        // 2. 判斷inheritThreadLocals 是否==true离唐, 父節(jié)點的inheritableThreadLocals是否不為空
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
          //3. 符合以上的話,那么創(chuàng)建當前線程的inheritableThreadLocals
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

步驟說明:

看著我上面標的步驟進行說明

1.獲取當前線程為父線程问窃,其實就是創(chuàng)建這個線程的線程

2.判斷inheritThreadLocals 是否==true 亥鬓, 默認inheritThreadLocals就是為true, 通用的new Thread()方法域庇,這個值就是true, 同時判斷父節(jié)點的inheritableThreadLocals是否為空贮竟, 如果不為空,則說明需要進行傳遞较剃。

3.在這個if里面咕别,針對當前線程做了inheritableThreadLocals的初始化, 把父線程的值拷貝到這個里面來写穴。

通過上面的分析惰拱,其實基本的原理都已經(jīng)了解清楚了,不熟悉的可以可以自己去細細研究啊送。

那么是否這種做法完全可以符合我們的需求呢偿短? 我們看一下下面的場景

線程池異常場景

線程池demo

/**
 * ce
 *
 * @author zhangyunhe
 * @date 2020-04-22 16:19
 */
public class InteritableTest {


    static ThreadLocal<String> local = new InheritableThreadLocal<>();

    static ExecutorService poolExecutor = Executors.newFixedThreadPool(1);

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        InteritableTest test = new InteritableTest();
        test.test();
    }
    private void test() throws ExecutionException, InterruptedException {


        local.set("天王老子");
        Future future = poolExecutor.submit(new Task("任務1"));

        future.get();

        Future future2 = poolExecutor.submit(new Task("任務2"));

        future2.get();

        System.out.println("父線程的值:"+local.get());
    }

    class Task implements Runnable{

        String str;
        Task(String str){
            this.str = str;
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+":"+local.get());
            local.set(str);
            System.out.println(local.get());
        }
    }

}

輸出結果:

pool-1-thread-1:天王老子
任務1
pool-1-thread-1:任務1
任務2
父線程的值:天王老子

從上面可以看到,Task2執(zhí)行的時候馋没,獲取到的父線程的值是Task1修改過的昔逗。 這樣感覺是不是就破壞了我們的本意? 實際上篷朵,這是因為我們使用了線程池勾怒,在池化技術里面,線程是會被復用的声旺,當執(zhí)行Task2的時候笔链,實際上是用的Task1的那個線程,那個線程已經(jīng)被創(chuàng)建好了的腮猖,所以那里面的locals就是被Task1修改過的鉴扫,那么遇到這種問題,該如何解決呢澈缺?

下一篇文章給大家介紹一個組件坪创,在使用線程池等會池化復用線程的執(zhí)行組件情況下炕婶,提供ThreadLocal值的傳遞功能

?著作權歸作者所有,轉載或內容合作請聯(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

推薦閱讀更多精彩內容