一個故事講明白線程的私家領地:ThreadLocal

 

原創 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等領域都能找到。

(完)

相關文章
相關標籤/搜索