终于弄明白了ThreadLocal
🌈寫SpringBoot項目的時候,經常用到的一個保存用戶信息的類就是Threadlocal,我們今天就來詳細介紹一下這個類。
Threadlocal有什么用:
📌簡單的說就是,一個ThreadLocal在一個線程中是共享的,在不同線程之間又是隔離的(每個線程都只能看到自己線程的值)。如下圖:
ThreadLocal使用實例
API介紹
🦄在使用Threadlocal之前我們先看以下它的API:
ThreadLocal類的API非常的簡單,在這里比較重要的就是get()、set()、remove(),set用于賦值操作,get用于獲取變量的值,remove就是刪除當前變量的值.需要注意的是initialValue方法會在第一次調用時被觸發,用于初始化當前變量值,默認情況下initialValue返回的是null。
ThreadLocal的使用
🍋說完了ThreadLocal類的API了,那我們就來動手實踐一下了,來理解前面的那句話:一個ThreadLocal在一個線程中是共享的,在不同線程之間又是隔離的(每個線程都只能看到自己線程的值)
public class ThreadLocalTest {private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {// 重寫這個方法,可以修改“線程變量”的初始值,默認是null@Overrideprotected Integer initialValue() {return 0;}};public static void main(String[] args) throws InterruptedException {//一號線程new Thread(new Runnable() {@Overridepublic void run() {System.out.println("一號線程set前:" + threadLocal.get());threadLocal.set(1);System.out.println("一號線程set后:" + threadLocal.get());}}).start();//二號線程new Thread(new Runnable() {@Overridepublic void run() {System.out.println("二號線程set前:" + threadLocal.get());threadLocal.set(2);System.out.println("二號線程set后:" + threadLocal.get());}}).start();//主線程睡1sThread.sleep(1000);//主線程System.out.println("主線程的threadlocal值:" + threadLocal.get());}}稍微解釋一下上面的代碼:💡
每一個ThreadLocal實例就類似于一個變量名,不同的ThreadLocal實例就是不同的變量名,它們內部會存有一個值(暫時這么理解)在后面的描述中所說的“ThreadLocal變量或者是線程變量”代表的就是ThreadLocal類的實例。
在類中創建了一個靜態的 “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可以實現線程之間隔離的原因了。
內部類ThreadLocalMap
ThreadLocalMap是ThreadLocal的內部類,實現了一套自己的Map結構?
ThreadLocalMap屬性:
static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}//初始容量16private static final int INITIAL_CAPACITY = 16;//散列表private Entry[] table;//entry 有效數量 private int size = 0;//負載因子private int threshold;ThreadLocalMap設置ThreadLocal 變量
private void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;int len = tab.length;//與運算 & (len-1) 這就是為什么 要求數組len 要求2的n次冪 //因為len減一后最后一個bit是1 與運算計算出來的數值下標 能保證全覆蓋 //否者數組有效位會減半 //如果是hashmap 計算完下標后 會增加鏈表 或紅黑樹的查找計算量 int i = key.threadLocalHashCode & (len-1);// 從下標位置開始向后循環搜索 不會死循環 有擴容因子 必定有空余槽點for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();//一種情況 是當前引用 返回值if (k == key) {e.value = value;return;}//槽點被GC掉 重設狀態 if (k == null) {replaceStaleEntry(key, value, i);return;}}//槽點為空 設置valuetab[i] = new Entry(key, value);//設置ThreadLocal數量int sz = ++size;//沒有可清理的槽點 并且數量大于負載因子 rehashif (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}ThreadLocalMap屬性介紹😎:
- 和普通Hashmap類似存儲在一個數組內,但與hashmap使用的拉鏈法解決散列沖突不同的是 ThreadLocalMap使用開放地址法
- 數組 初始容量16,負載因子2/3
- node節點 的key封裝了WeakReference 用于回收
ThreadLocalMap存儲位置
儲存在Thread中,有兩個ThreadLocalMap變量
注意:
Key的弱引用問題
為什么要用弱引用,官方是這樣回答的
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
為了處理非常大和生命周期非常長的線程,哈希表使用弱引用作為 key。
生命周期長的線程可以理解為:線程池的核心線程
ThreadLocal在沒有外部對象強引用時如Thread,發生GC時弱引用Key會被回收,而Value是強引用不會回收,如果創建ThreadLocal的線程一直持續運行如線程池中的線程,那么這個Entry對象中的value就有可能一直得不到回收,發生內存泄露。
- key 使用強引用🌴: 引用的ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強引用,如果沒有手動刪除,ThreadLocal不會被回收,導致Entry內存泄漏。
- key 使用弱引用🌴: 引用的ThreadLocal的對象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal也會被回收。value在下一次ThreadLocalMap調用set,get,remove的時候會被清除。
Java8中已經做了一些優化如,在ThreadLocal的get()、set()、remove()方法調用的時候會清除掉線程ThreadLocalMap中所有Entry中Key為null的Value,并將整個Entry設置為null,利于下次內存回收。
java中的四種引用
通常ThreadLocalMap的生命周期跟Thread(注意線程池中的Thread)一樣長,如果沒有手動刪除對應key(線程使用結束歸還給線程池了,其中的KV不再被使用但又不會GC回收,可認為是內存泄漏),一定會導致內存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal會被GC回收,不會內存泄漏,對應的value在下一次ThreadLocalMap調用set,get,remove的時候會被清除,Java8已經做了上面的代碼優化。
總結:
ThreadLocal的作用: 實現線程范圍內的局部變量,即ThreadLocal在一個線程中是共享的,在不同線程之間是隔離的。
ThreadLocal的原理: ThreadLocal存入值時使用當前ThreadLocal實例作為key(并不是以當前線程對象作為key),存入當前線程對象中的Map中去。最開始在看源碼之前,我以為是以當前線程對象作為key將對象存入到ThreadLocal中的Map中去…
🚀🚀🚀
總結
以上是生活随笔為你收集整理的终于弄明白了ThreadLocal的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA计算机毕业设计电子产品购物平台M
- 下一篇: git 换行符LF与CRLF转换问题