簡介
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()方法
1 public void set(T value) { 2 Thread t = Thread.currentThread(); //獲得當前線程 3 ThreadLocalMap map = getMap(t); //將當前線程作為參數傳入,來獲取ThreadLocalMap 4 if (map != null) 5 map.set(this, value); 6 else 7 createMap(t, value); 8 }
然后進入getMap()方法
1 ThreadLocalMap getMap(Thread t) { 2 return t.threadLocals; //追蹤后發現t.threadLocals就是 ThreadLocal.ThreadLocalMap threadLocals; 3 }
如果得到的map為null,那么說明是第一次,走createMap方法創建
1 void createMap(Thread t, T firstValue) { 2 t.threadLocals = new ThreadLocalMap(this, firstValue); //看到了吧,ThreadLocalMap直接是給Thread保存的 3 }
進入new ThreadLocalMap方法,這里注意,傳入的this就是指ThreadLocal
1 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { //看到參數名字沒,firstKey,ThreadLocal傳進來是當作key值的! 2 table = new Entry[INITIAL_CAPACITY]; //table其實是private Entry[] table; 一個數組而已 3 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 4 table[i] = new Entry(firstKey, firstValue); 5 size = 1; 6 setThreshold(INITIAL_CAPACITY); 7 }
ThreadLocalMap沒有實現Map接口,跟Map沒半毛錢關系,至於Entry是什么,我們看看
1 static class Entry extends WeakReference<ThreadLocal<?>> { 2 /** The value associated with this ThreadLocal. */ 3 Object value; 4 5 Entry(ThreadLocal<?> k, Object v) { 6 super(k); 7 value = v; 8 } 9 }
首先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連不迷路~~