ThreadLocal原理記錄,別被坑了!!
簡介
ThreadLocal的用處
ThreadLocal是為了將資料記錄一份到某個執行緒裡,確保該資料執行緒安全
例如資料庫的Connection放入ThreadLocal,一個事務會用到很多DAO,但只能用共同的Connection,這樣才能保證事務完整性
所以當某個類的其中一個變數,會被同一個執行緒多次使用,並且還嚴格的規定每次都得是這個變數操作
那麼就能把這個變數放入ThreadLocal
Spring也是將各種Bean放入ThreadLocal中來確保Bean的“無狀態”化
吐槽開始!!
今天翻書看了關於ThreadLocal的介紹,和網上一些關於ThreadLocal的部落格,這個原理介紹真的是坑,大錯特錯
大家可能都看到過下面這種所謂的ThreadLocal簡單的實現思路介紹:
完了後還加上一句:
雖然上面的程式碼清單中的這個ThreadLocal實現版本顯得比較簡單粗爆,但其目的主要在與呈現JDK中所提供的ThreadLocal類在實現上的思路
上面這樣簡化ThreadLocal實現根本錯的離譜
不僅是有的部落格這樣,包括書本也是這樣介紹的,傳播知識給他人,的確可以簡化程式碼實現,但不等於更改了正確的實現思路!
這樣會誤導他人對ThreadLocal的進一步學習
ThreadLocal真正的實現方式
先說總結,跟上面錯誤做對比
1.ThreadLocalMap 別看有個Map結尾,其實壓根就是重新實現的類
跟Map沒半毛錢關係,沒實現Map介面的,沒用HashMap,別覺得根據key找value就只能使用map了
2.執行緒根據key找對應的value,這個key並不是執行緒id,而是ThreadLocal類
為什麼,因為ThreadLocalMap是存放線上程裡的,每個執行緒都只有一個只屬於自己的ThreadLocalMap
這樣的話存個毛的執行緒id,有什麼意義?
揭開Thread,ThreadLocal,ThreadLocalMap真正的關係
首先進入ThreadLocal,發現如下
ThreadLocalMap實在ThreadLocal裡實現的
直接找到ThreadLocal的set()方法
public void set(T value) { Thread t = Thread.currentThread(); //獲得當前執行緒 ThreadLocalMap map = getMap(t); //將當前執行緒作為引數傳入,來獲取ThreadLocalMap if (map != null) map.set(this, value); else createMap(t, value); }
然後進入getMap()方法
ThreadLocalMap getMap(Thread t) { return t.threadLocals; //追蹤後發現t.threadLocals就是 ThreadLocal.ThreadLocalMap threadLocals; }
如果得到的map為null,那麼說明是第一次,走createMap方法建立
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); //看到了吧,ThreadLocalMap直接是給Thread儲存的 }
進入new ThreadLocalMap方法,這裡注意,傳入的this就是指ThreadLocal
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { //看到引數名字沒,firstKey,ThreadLocal傳進來是當作key值的! table = new Entry[INITIAL_CAPACITY]; //table其實是private Entry[] table; 一個數組而已 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }
ThreadLocalMap沒有實現Map介面,跟Map沒半毛錢關係,至於Entry是什麼,我們看看
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
首先WeakReference是指弱引用的意思,繼承了這玩意有以下效果:
當一個物件僅僅被weak reference(弱引用)指向, 而沒有任何其他strong reference(強引用)指向的時候, 如果這時GC執行, 那麼這個物件就會被回收,不論當前的記憶體空間是否足夠,這個物件都會被回收。
好處在於,如果某個ThreadLocal被回收了,那麼ThreadLocalMap的這個Key在下次GC()的時候也會被回收(不然就造成記憶體洩露了,這個key永遠不會被呼叫)
Entry是一個ThreadLocalMap的內部類,跟Map的Entry實現有點像,有key和value的定義(俗稱 桶)
ok,我們發現,ThreadLocalMap建立後,就是初始化一個Entry的陣列,生成一個Entry將ThreadLocal作為key值,將要存的值作為value放入其中存好
那麼假設現在ThreadLocalMap已經存在,走的是第一個分支,直接set,我們看看他怎麼實現的
1 private void set(ThreadLocal<?> key, Object value) { 2 3 Entry[] tab = table; 4 int len = tab.length; 5 int i = key.threadLocalHashCode & (len-1); 6 7 for (Entry e = tab[i]; 8 e != null; 9 e = tab[i = nextIndex(i, len)]) { 10 ThreadLocal<?> k = e.get(); 11 12 if (k == key) { 13 e.value = value; 14 return; 15 } 16 17 if (k == null) { 18 replaceStaleEntry(key, value, i); 19 return; 20 } 21 } 22 23 tab[i] = new Entry(key, value); 24 int sz = ++size; 25 if (!cleanSomeSlots(i, sz) && sz >= threshold) 26 rehash(); 27 }
看到第7行的for沒有,直接遍歷方才說的Entry陣列,將ThreadLocal取出來比較(相當於key比較),匹配就設定value,沒這個key就存起來
ThreadLocalMap為啥不用HashMap而是自己陣列實現
有key和value這個概念出現,也不是一定要用HashMap這些的,為什麼用陣列
個人覺得是省開銷,建立Map物件的開銷和使用Map的開銷,畢竟ThreadLocalMap初始預設長度為16,而真實情況中一個執行緒不會有這麼本地變數要儲存
所以,當使用ThreadLocal來存的時候,ThreadLocal會建立一個ThreadLocalMap給呼叫它的執行緒,自己作為key值去儲存
取值的時候,ThreadLocal拿Thread裡儲存的ThreadLocal,然後將自身作為key值去取值
為什麼ThreadLocalMap的程式碼不放在Thread中
網上有個答案我覺得很合理:
將ThreadLocalMap定義在Thread類內部看起來更符合邏輯 但是ThreadLocalMap並不需要Thread物件來操作,所以定義在Thread類內只會增加一些不必要的開銷。 定義在ThreadLocal類中的原因是ThreadLocal類負責ThreadLocalMap的建立和使用 總的來說就是,ThreadLocalMap不是必需品,定義在Thread中增加了成本,定義在ThreadLocal中按需建立。
執行緒不一定都用到ThreadLocal的哦,如果不使用,就不會被賦值一個ThreadLocalMap
換個思維,這其實也是種不錯的設計模式:
一個工具類把自身當作唯一標識,去操作工具類本身的方法,只需要將資料記錄給呼叫它的類就好
ThreadLocal的記憶體洩露問題
正常來說,我們建立一個執行緒,跑完後會銷燬,自動呼叫ThreadLocal的remove()方法,清除ThreadLocalMap的內容
但是,實際中我們是使用執行緒池的,而執行緒跑完後會返回執行緒池中,並不會銷燬
這時候的ThreadLocalMap的內容就還在的(記憶體就是這裡洩露啦)
所以,線上程池中用ThreadLocal,記得run()要跑完時用下remove()方法清除ThreadLocalMap中的key
至此分享完畢啦,希望大家點點贊,或者一鍵3連不迷路~~<