背景: 今天在開發(fā)項目的時候碰到一個線程安全的問題诉位。具體的情況是這樣的赎婚,服務(wù)是基于dubbo對外提供服務(wù)的逊彭。所有的請求就是java服務(wù)多線程處理的瓦戚。我寫的一個類里面有多個方法需要重復(fù)的查詢DB來獲取數(shù)據(jù)团驱,使用希望定義一個全局變量來保存摸吠,只需要查詢一次即可。但是由于是一個靜態(tài)變量在多線程環(huán)境下面是不安全的嚎花。所以需要一個辦法來讓靜態(tài)變量在線程與線程直接互相獨立寸痢,互不干擾。后經(jīng)同事的提醒贩幻,可以用ThreadLocal來解決轿腺,所以先對這個類進(jìn)行了一次了解。
探索過程
首先要了解java變量作用域的問題丛楚。
靜態(tài)變量: 線程非安全族壳。靜態(tài)變量即類變量,位于方法區(qū)趣些,為所有對象共享仿荆,共享一份內(nèi)存,一旦靜態(tài)變量被修改,其他對象均對修改可見拢操,故線程非安全锦亦。
實例變量: 單例模式(只有一個對象實例存在)線程非安全,非單例線程安全令境。實例變量為對象實例私有杠园,在虛擬機的堆中分配,若在系統(tǒng)中只存在一個此對象的實例舔庶,在多線程環(huán)境下抛蚁,“猶如”靜態(tài)變量那樣,被某個線程修改后惕橙,其他線程對修改均可見瞧甩,故線程非安全;如果每個線程執(zhí)行都是在不同的對象中,那對象與對象之間的實例變量的修改將互不影響,故線程安全嚷兔。
局部變量: 線程安全芭挽。每個線程執(zhí)行時將會把局部變量放在各自棧幀的工作內(nèi)存中,線程間不共享,故不存在線程安全問題。
這里我使用的是靜態(tài)變量,多線程下面變量的內(nèi)存區(qū)域是共享的思灰。這就意味著當(dāng)一個線程給這個變量負(fù)責(zé)了a下面的邏輯需要這個變量為a才能保證邏輯正確,但是此時另外一個線程將這個變量改成了b混滔。咔~~洒疚,程序邏輯就錯了。而且這種還不是語法錯坯屿,還是偶然性的油湖,出問題的時候可能就需要花很多時間來排查問題。
那ThreadLocal是怎么避免這個問題呢.
原理大概是一個線程的map领跛,可以理解為Map<Thread, Map<K, V>>這樣的結(jié)果乏德,Thread就是線程號了,下面的map就是我們使用存儲的值吠昭。
詳細(xì)內(nèi)容看這里深入剖析ThreadLocal實現(xiàn)原理以及內(nèi)存泄漏問題
在這篇文章里面同時也引入了另外一個問題喊括,ThreadLocal內(nèi)存泄露的問題。
ThreadLocalMap使用ThreadLocal的弱引用作為key矢棚,如果一個ThreadLocal沒有外部強引用來引用它郑什,那么系統(tǒng) GC 的時候,這個ThreadLocal勢必會被回收蒲肋,這樣一來蘑拯,ThreadLocalMap中就會出現(xiàn)key為null的Entry钝满,就沒有辦法訪問這些key為null的Entry的value,如果當(dāng)前線程再遲遲不結(jié)束的話申窘,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠(yuǎn)無法回收弯蚜,造成內(nèi)存泄漏。
其實剃法,ThreadLocalMap的設(shè)計中已經(jīng)考慮到這種情況碎捺,也加上了一些防護(hù)措施:在ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap里所有key為null的value。
順便去喵了一眼這個博主的博客贷洲,才知道他還沒有畢業(yè)牵寺。看看他在大學(xué)里面干的事情恩脂,再回想自己當(dāng)年大學(xué)里面是浪費了多少時間。多么無知趣斤。
最后帶一個使用方法吧俩块,看這些博文里面都沒有寫怎么用
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
threadLocal.set(100L);
threadLocal.get();
threadLocal.remove();