本文轉載自公衆号 碼農翻身
張大胖上午遇到了一個棘手的問題,他在一個AccountService中寫了一段類似這樣的代碼:
然後這個AccountService 調用了其他Java類,不知道經過了多少層調用以後,最終來到了一個叫做AccountUtil的地方,在這個類中需要使用Context中的trackerID來做點兒事情:

很明顯,這個AccountUtil沒有辦法拿到Context對象, 怎麼辦?
張大胖想到,要不把Context對象一層層地傳遞下去,這樣AccountUtil不就可以得到了嗎?
可是這麼做改動量太大!涉及到的每一層函數調用都得改動,有很多類都不屬于自己的小組管理,還得和别人協調。
更要命的是有些類根本就沒有源碼,想改都改不了。
這也難不住我,張大胖想:可以把那個set/get TrackerID的方法改成靜态(static)的,這樣不管跨多少層調用都沒有問題!
這樣就不用一層層地傳遞了,Perfect!
張大胖得意洋洋地把代碼送出給Bill做Review。
Bill看了一眼就指出了緻命的問題: 多線程并發的時候出錯!
張大胖恨不得找個地縫鑽進去:又栽在多線程上面了,這次犯的還是低級錯誤!
線程1調用了Context.setTrackerID(), 線程2 也調用了Context.setTrackerID(),資料互相覆寫,不出亂子才怪。
張大胖感慨地說:“像我這樣中情況,需要在某處設定一個值,然後經過重重方法調用,到了另外一處把這個值取出來,又要線程安全,實在是不好辦啊, 對了,我能不能把這個值就放到線程中? 讓線程攜帶着這個值到處跑,這樣我無論在任何地方都可以輕松獲得了!”
Bill說:“有啊,每個線程都有一個私家領地! 在Thread這個類中有個專門的資料結構,你可以放入你的TrackerID,然後到任何地方都可以把這個TrackerID給取出來。”
“這麼好? ”
張大胖打開JDK中的Thread類,仔細檢視,果然在其中有個叫做threadLocals的變量,還是個Map類型 , 但是在Thread類中卻沒有對這個變量操作的方法。
看到張大胖的疑惑,Bill說:“也許你注意到了,這個變量不是通過Thread的通路的,對他的通路委托給了ThreadLocal這個類。”
“那我怎麼使用它?”
“非常簡單, 你可以輕松建立一個ThreadLocal類的執行個體:
像‘1234’, ‘5678’這些值都會放到自己所屬的線程對象中。”
“等你使用的時候,可以這麼辦:”
“明白了,相當于把各自的資料放入到了各自Thread這個對象中去了,每個線程的值自然就區分開了。 可是我不明白的是為什麼那個資料結構是個map 呢?”
“你想想,假設你建立了另外一個threadLocalB:”
那線程對象的Map就起到作用了:
“明白了,這個私家領地還真是好用,我現在就把我那個Context給改了,讓它使用ThreadLocal:”
小結:
ThreadLocal這個名字起得有點讓人誤解, 很容易讓人認為是“本地線程”, 其實是用來維護本線程的變量。 對照着上面的原理講解,我想大家可以自行去看ThreadLocal的源碼,輕松了解。
ThreadLocal 并不僅僅是Java中的概念,其他語言例如Python,C#中也有,作用類似。
ThreadLocal在日常工作中用得不多,但是在架構(如Spring)中是個基礎性的技術,在事務管理,AOP等領域都能找到。