ThreadLocal,Thread和ThreadLocalMap分享


簡介

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連不迷路~~


免責聲明!

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



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