張大胖上午遇到了一個棘手的問題,他在一個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等領域都能找到。