原創 2018-01-31 老劉 碼農翻身安全
張大胖上午遇到了一個棘手的問題,他在一個AccountService中寫了一段相似這樣的代碼:數據結構
Context ctx = new Context(); ctx.setTrackerID(.....)
而後這個AccountService 調用了其餘Java類,不知道通過了多少層調用之後,最終來到了一個叫作AccountUtil的地方,在這個類中須要使用Context中的trackerID來作點兒事情:多線程
很明顯,這個AccountUtil沒有辦法拿到Context對象, 怎麼辦?併發
張大胖想到,要不把Context對象一層層地傳遞下去,這樣AccountUtil不就能夠獲得了嗎?框架
但是這麼作改動量太大!涉及到的每一層函數調用都得改動,有不少類都不屬於本身的小組管理,還得和別人協調。 函數
更要命的是有些類根本就沒有源碼,想改都改不了。ui
這也難不住我,張大胖想:能夠把那個set/get TrackerID的方法改爲靜態(static)的,這樣無論跨多少層調用都沒有問題!spa
public class Context{ public static String getTrackerID(){ ...... } public static void setTrackerID(String id){ ...... } }
這樣就不用一層層地傳遞了,Perfect!線程
張大胖得意洋洋地把代碼提交給Bill作Review。 3d
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等領域都能找到。
(完)