通常我們為了規避單例模式下多線程之間共享全局變量的問題,會使用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()方法,避免產生內存溢出。
完畢。