歡迎關注公衆號【sharedCode】致力於主流中間件的源碼分析, 我的網站:https://www.shared-code.com/java
前言
在實際的工做中,有些時候咱們會遇到一些線程之間共享數據的問題,好比一下幾個典型的業務場景,舉幾個例子,你們可以更加直觀的感覺到異步
-
分佈式鏈路追蹤系統分佈式
這個沒必要多說,若是咱們須要記錄系統的每一個調用鏈路,那麼每個子系統裏面,若是調用了異步線程來作處理的話,那麼相似這種鏈路是否是須要收集起來呢?ide
-
日誌收集記錄系統上下文工具
在實際的日誌打印記錄中,一個http請求進來的話,每一行日誌,日誌產生的線程信息?上下文信息,是否是須要記錄下來呢?源碼分析
上面我舉的是咱們最最最多見的兩個例子了, 作了幾年開發的都能理解爲啥要有這個東西,下面咱們仔細聊一下這個問題,網站
InheritableThreadLocal
其實說到這個問題,有些同窗就會想到InheritableThreadLocal
這個工具了,這是JDK給咱們提供的的工具,該工具能夠解決父子線程之間的值的傳遞,咱們先來一個簡單的demo, 而後再進行原理分析this
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裏面設置的值,這就實現了值的傳遞了。日誌
源碼分析
下面咱們從源碼的角度上看一下InheritableThreadLocal
的實現,他到底是怎麼作到父子線程之間線程的傳遞的。
咱們首先看一下Thread建立的代碼。
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. 獲取當前線程爲父線程,其實就是建立這個線程的線程 Thread parent = currentThread(); // 省略代碼。。。。。 // 2. 判斷inheritThreadLocals 是否==true, 父節點的inheritableThreadLocals是否不爲空 if (inheritThreadLocals && parent.inheritableThreadLocals != null) //3. 符合以上的話,那麼建立當前線程的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.獲取當前線程爲父線程,其實就是建立這個線程的線程
2.判斷inheritThreadLocals 是否==true , 默認inheritThreadLocals就是爲true, 通用的new Thread()方法,這個值就是true, 同時判斷父節點的inheritableThreadLocals是否爲空, 若是不爲空,則說明須要進行傳遞。
3.在這個if裏面,針對當前線程作了inheritableThreadLocals的初始化, 把父線程的值拷貝到這個裏面來。
經過上面的分析,其實基本的原理都已經瞭解清楚了,不熟悉的能夠能夠本身去細細研究。
那麼是否這種作法徹底能夠符合咱們的需求呢? 咱們看一下下面的場景
線程池異常場景
線程池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執行的時候,獲取到的父線程的值是Task1修改過的。 這樣感受是否是就破壞了咱們的本意? 實際上,這是由於咱們使用了線程池,在池化技術裏面,線程是會被複用的,當執行Task2的時候,其實是用的Task1的那個線程,那個線程已經被建立好了的,因此那裏面的locals就是被Task1修改過的,那麼遇到這種問題,該如何解決呢?
下一篇文章給你們介紹一個組件,在使用線程池等會池化複用線程的執行組件狀況下,提供ThreadLocal值的傳遞功能