ThreadLocal原理詳解——終於弄明白了ThreadLocal
概述
在java學習生涯中可能很多人都會聽到ThreadLocal變數,從字面上理解ThreadLocal就是“執行緒區域性變數”的意思。簡單的說就是,一個ThreadLocal在一個執行緒中是共享的,在不同執行緒之間又是隔離的(每個執行緒都只能看到自己執行緒的值)。可能一開始把這句話放出來很難理解,那我們就繼續往後面看吧。
API介紹
再學習一個類之前我們需要了解一個類的API,這也是我們學習類的入口。而ThreadLocal類的API相當簡單。
在這裡面比較重要的就是,get、set、remove了,這三個方法是對這個變數進行操作的關鍵。set用於賦值操作,get用於獲取變數中的值,remove就是刪除當前這個變數的值。為什麼我們將ThreadLocal說成變數,我們姑且可以這麼理解,每個ThreadLocal例項中都可以儲存一個常量或者一個物件,而內部儲存的值是可以修改的,而這樣的特性與變數的特性及其相似,變數不就是用來儲存一個值的嗎?
也就是說每一個ThreadLocal例項就類似於一個變數名,不同的ThreadLocal例項就是不同的變數名,它們內部會存有一個值(暫時這麼理解)在後面的描述中所說的“ThreadLocal變數或者是執行緒變數”代表的就是ThreadLocal類的例項。
這裡還需要介紹一下initialValue方法,我麼都知道在Java中成員變數都會有預設值,而ThreadLocal做變數也會有預設值,那我們可以通過重寫initialValue方法指定ThreadLocal變數的初始值。預設情況下initialValue返回的是null。
ThreadLocal的理解
說完了ThreadLocal類的API了,那我們就來動手實踐一下了,來理解前面沒有理解的那句話:一個ThreadLocal在一個執行緒中是共享的,在不同執行緒之間又是隔離的(每個執行緒都只能看到自己執行緒的值)
public class ThreadLocalTest { private static ThreadLocal<Integer> num = new ThreadLocal<Integer>() { // 重寫這個方法,可以修改“執行緒變數”的初始值,預設是null @Override protected Integer initialValue() { return 0; } }; public static void main(String[] args) { // 建立一號執行緒 new Thread(new Runnable() { @Override public void run() { // 在一號執行緒中將ThreadLocal變數設定為1 num.set(1); System.out.println("一號執行緒中ThreadLocal變數中儲存的值為:" + num.get()); } }).start(); // 建立二號執行緒 new Thread(new Runnable() { @Override public void run() { num.set(2); System.out.println("二號執行緒中ThreadLocal變數中儲存的值為:" + num.get()); } }).start(); //為了讓一二號執行緒執行完畢,讓主執行緒睡500ms try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("主執行緒中ThreadLocal變數中儲存的值:" + num.get()); } }
稍微解釋一下上面的程式碼:
在類中建立了一個靜態的“ThreadLocal變數”,在主執行緒中建立兩個執行緒,在這兩個執行緒中分別設定ThreadLocal變數為1和2。然後等待一號和二號執行緒執行完畢後,在主執行緒中檢視ThreadLocal變數的值。
程式結果及分析
程式結果重點看的是主執行緒輸出的是0,如果是一個普通變數,在一號執行緒和二號執行緒中將普通變數設定為1和2,那麼在一二號執行緒執行完畢後在列印這個變數,輸出的值肯定是1或者2(到底輸出哪一個由作業系統的執行緒排程邏輯有關)。但使用ThreadLocal變數通過兩個執行緒賦值後,在主執行緒程中輸出的卻是初始值0。在這也就是為什麼“一個ThreadLocal在一個執行緒中是共享的,在不同執行緒之間又是隔離的”,每個執行緒都只能看到自己執行緒的值,這也就是ThreadLocal的核心作用:實現執行緒範圍的區域性變數。
ThreadLocal的原理分析
老規矩我們還是將最後結論擺在前面,每個Thread物件都有一個ThreadLocalMap,當建立一個ThreadLocal的時候,就會將該ThreadLocal物件新增到該Map中,其中鍵就是ThreadLocal,值可以是任意型別。這句話看不懂很正常,等我們一起看完原始碼以後就明白了。
此時就需要糾正前面提到的錯誤觀點了,前面我們的理解是所有的常量值或者是引用型別的引用都是儲存在ThreadLocal例項中的,但實際上不是的,這種說法只是讓我們更好的理解ThreadLocal變數這個概念。向ThreadLocal存入一個值,實際上是向當前執行緒物件中的ThreadLocalMap存入值,ThreadLocalMap我們可以簡單的理解成一個Map,而向這個Map存值的key就是ThreadLocal例項本身。
也就是說,想要存入的ThreadLocal中的資料實際上並沒有存到ThreadLocal物件中去,而是以這個ThreadLocal例項作為key存到了當前執行緒中的一個Map中去了,獲取ThreadLocal的值時同樣也是這個道理。這也就是為什麼ThreadLocal可以實現執行緒之間隔離的原因了。
總結
ThreadLocal的作用:實現執行緒範圍內的區域性變數,即ThreadLocal在一個執行緒中是共享的,在不同執行緒之間是隔離的。
ThreadLocal的原理:ThreadLocal存入值時使用當前ThreadLocal例項作為key,存入當前執行緒物件中的Map中去。最開始在看原始碼之前,我以為是以當前執行緒物件作為key將物件存入到ThreadLocal中的Map中去....