一個故事講明白 ThreadLocal,是真的一看就懂!!!

張大胖上午遇到了一個棘手的問題,他在一個AccountService中寫了一段類似這樣的代碼:

Context ctx = new Context();
ctx.setTrackerID(.....)


然後這個AccountService 調用了其他Java類,不知道經過了多少層調用以後,最終來到了一個叫做AccountUtil的地方,在這個類中需要使用Context中的trackerID來做點兒事情:



很明顯,這個AccountUtil沒有辦法拿到Context對象, 怎麼辦?


張大胖想到,要不把Context對象一層層地傳遞下去,這樣AccountUtil不就可以得到了嗎?



可是這麼做改動量太大!涉及到的每一層函數調用都得改動,有很多類都不屬於自己的小組管理,還得和別人協調。 


更要命的是有些類根本就沒有源碼,想改都改不了。


這也難不住我,張大胖想:可以把那個set/get TrackerID的方法改成靜態(static)的,這樣不管跨多少層調用都沒有問題!


public class Context{
   public static String getTrackerID(){
       ......
   }
   public static void setTrackerID(String id){
       ......
   }
}



這樣就不用一層層地傳遞了,Perfect!


張大胖得意洋洋地把代碼提交給Bill做Review。 


Bill看了一眼就指出了致命的問題: 多線程併發的時候出錯!


張大胖恨不得找個地縫鑽進去:又栽在多線程上面了,這次犯的還是低級錯誤!


線程1調用了Context.setTrackerID(), 線程2 也調用了Context.setTrackerID(),數據互相覆蓋,不出亂子纔怪。


張大胖感慨地說:“像我這樣中情況,需要在某處設置一個值,然後經過重重方法調用,到了另外一處把這個值取出來,又要線程安全,實在是不好辦啊, 對了,我能不能把這個值就放到線程中? 讓線程攜帶着這個值到處跑,這樣我無論在任何地方都可以輕鬆獲得了!”


Bill說:“有啊,每個線程都有一個私家領地! 在Thread這個類中有個專門的數據結構,你可以放入你的TrackerID,然後到任何地方都可以把這個TrackerID給取出來。”


“這麼好? ” 


張大胖打開JDK中的Thread類,仔細查看,果然在其中有個叫做threadLocals的變量,還是個Map類型 , 但是在Thread類中卻沒有對這個變量操作的方法。 


看到張大胖的疑惑,Bill說:“也許你注意到了,這個變量不是通過Thread的訪問的,對他的訪問委託給了ThreadLocal這個類。


“那我怎麼使用它?”


“非常簡單, 你可以輕鬆創建一個ThreadLocal類的實例:


ThreadLocal<String> threadLocalA= new ThreadLocal<String>();

線程1: threadLocalA.set("1234");
線程2: threadLocalA.set("5678");


像‘1234’, ‘5678’這些值都會放到自己所屬的線程對象中。”



“等你使用的時候,可以這麼辦:”


線程1: threadLocalA.get()  --> "1234"
線程2: threadLocalA.get() --> "5678"


“明白了,相當於把各自的數據放入到了各自Thread這個對象中去了,每個線程的值自然就區分開了。 可是我不明白的是爲什麼那個數據結構是個map 呢?”


“你想想,假設你創建了另外一個threadLocalB:”


ThreadLocal<Integer> threadLocalB = new ThreadLocal<Integer>();

線程1: threadLocalB.set(30);
線程2: threadLocalB.set(40);


那線程對象的Map就起到作用了:



“明白了,這個私家領地還真是好用,我現在就把我那個Context給改了,讓它使用ThreadLocal:”

public class Context {
   private static final ThreadLocal<String> mThreadLocal
       = new ThreadLocal<String>();

   public static void setTrackerID(String id) {
       mThreadLocal.set(id);
   }  
   public static String getTrackerID() {
       return mThreadLocal.get();
   }  

}


小結:


ThreadLocal這個名字起得有點讓人誤解, 很容易讓人認爲是“本地線程”, 其實是用來維護本線程的變量。 對照着上面的原理講解,我想大家可以自行去看ThreadLocal的源碼,輕鬆理解。


ThreadLocal 並不僅僅是Java中的概念,其他語言例如Python,C#中也有,作用類似。


ThreadLocal在日常工作中用得不多,但是在框架(如Spring)中是個基礎性的技術,在事務管理,AOP等領域都能找到。

原文鏈接:https://mp.weixin.qq.com/s/aM03vvSpDpvwOdaJ8u3Zgw

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章