ThreadLocal是如何存儲數據的?理解源碼,規避ThreadLocal坑


通常我們為了規避單例模式下多線程之間共享全局變量的問題,會使用ThreadLocal,不過很多人不知道ThreadLocal里數據的存儲方式和我們平時理解的不太一樣。

 

源碼解析(直奔主題):

每個線程都有一個ThreadLocalMap對象:

 

 

這個Map里存了所有線程變量中保存的數據,保存的方式:

 

以ThreadLocal對象作為key,ThreadLocal里“存放”的數據作為value,放在該Map中。

 

 

 

在我們創建了一個線程變量 maxLife=new ThreadLocal<Integer>(),之后執行其set(xxx)方法,其實是以maxLife這個對象為鍵,以xxx為值,將這組鍵值對放入當前線程的ThreadLocalMap對象中。

 

這種方式的存儲有一個坑必須注意:

ThreadLocal對象一旦創建不可輕易替換,替換會導致所有共享該ThreadLocal對象的線程均無法找到之前為其“保存”的值。

 

打個比方

張三(Thread)和李四(Thread)各有一個一模一樣盒子(ThreadLocalMap)。

這是個神奇的盒子,可以用任何一個鑰匙(ThreadLocal對象)打開,每個鑰匙打開盒子都會打開他們自己的存儲空間,和別的鑰匙互不干擾。

湊巧張三李四共用一個鑰匙包(ThreadLocal對象的引用),這個鑰匙包里只存放了一個鑰匙(ThreadLocal對象)。

有一天張三用這個鑰匙包里的鑰匙存了一個兔子在盒子里,李四也做了同樣的事

接着張三把鑰匙包里的鑰匙換掉了,那么李四再用這個鑰匙包里的鑰匙去開盒子,會發現,兔子不見了!

 

通過偽代碼再現坑

為了說明這個坑,我們再上一些偽代碼:

首先,我們在類中定義一個全局變量,后邊的所有線程都會共享這個變量。

ThreadLocal<Integer> maxLife = null;

 

然后創建一個線程A,並在這個線程中為maxLife賦一個新對象,並且為其設置值

maxLife = new ThreadLocal<Integer>()
maxLife.set(0);

 

之后暫停A線程的執行,同時我們創建一個B線程,然后在B線程里,將maxLife指向一個新的對象

 

maxLife = new ThreadLocal<Integer>();

 

執行完這一段,暫停B的執行,然后再回到A線程,執行

Integer i = maxLife.get();

 

這個時候你如果打印i,那么你會看到一個null而不是0。

 

這是為什么呢?

因為maxLife的get方法,其實是以maxLife這個對象本身為鍵,然后去當前線程的ThreadLocalMap中獲取其所對應的值,A線程中我們new了一個對象然后當成key,B線程中我們又new了一個,我們知道A和B先后new 的兩個對象不是同一個對象,那么以不同的對象作為key去一個map里面取值會怎樣呢?

 

也就是說 maxLife 這個變量,在A和B中是共享的,但是maxLife作為key在A和B各自的ThreadLocalMap中對應的值是不一樣的。

 

總結

所以我們定義ThreadLocal變量,應該在一開始就為其new一個值,使用過程中避免修改其引用指向對象,而不是讓它等於null。

並且使用完畢后調用其remove()方法,避免產生內存溢出。

 

 

完畢。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM