執行緒基礎知識08- ThreadLocal基礎總結
ThreadLocal在平時開發中是用的比較多的一個類,主要用於儲存執行緒的資料。下面我對ThreadLocal進行一下總結;
dreamcatcher-cx大佬對ThreadLocal的設計總結,寫的比較深刻,很有幫助。
主要使用場景:
-
多資料來源切換,記錄當前執行緒訪問的資料來源
-
spring框架事務管理,用於存放事務資料;
-
springsecurity安全框架,用於儲存使用者登入資訊;
除了以上還有很多,不限於以上。
解讀原始碼
資料儲存ThreadLockMap
-
通過Entry陣列進行儲存的;
-
Entry繼承Reference類,是弱關聯的類,當ThreadLocal的例項為空時,GC會快速收回;一般用於處理資料量較大且維持時間較短的業務。
-
Entry節點儲存的是ThreadLocal物件和物件值
//是通過Entry陣列進行儲存的 private Entry[] table; static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
set 方法
-
存放ThreadLocal<?>的是弱關聯的容器,GC會等儲存滿的時候處理;
-
存放的過程中,會對key判斷,調整陣列位置;
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1);//獲取存放的位置 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get();//獲取存入的ThreadLock if (k == key) {//判斷是否一致,如果一致,替換值並返回 e.value = value; return; } if (k == null) {//如果i節點獲取到的ThreadLocal為空的話 /** * 1.從i 的向前迴圈查詢tab[i]節點的ThreadLocal不為空的位置slotToExpunge; * 2.從i以下節點迴圈遍歷,會有兩種情況; * 3.第一種情況:找到對應key相等的節點,和當前遍歷的節點位置進行交換;如果slotToExpunge和i節點相同,則進行rehash * 4.第二種情況:key為空,並且slotToExpunge和遍歷的位置相等的情況下,slotToExpunge == 遍歷的位置; * 5.根據傳入的i和slotToExpunge是否相等,進行陣列的節點的清除和rehash操作 */ replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value);//新增信的節點 int sz = ++size; /** * 判斷是不是要進行擴容 * Entry陣列的擴容是2倍擴容的; */ if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
get方法
- 獲取儲存的Entry節點
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
/**
* 從i位置往後逐個遍歷查詢如果找到了就返回,沒找到返回null;
*/
return getEntryAfterMiss(key, i, e);
}
remove方法
- 查詢對應資料節點進行刪除就行
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
ThreadLocal類的方法
set方法資料儲存
-
首次儲存,新建一個ThreadLockMap,以當前ThreaLock為key;
-
如果執行緒的儲存區域已經初始化過,則更新儲存區域中的資料;
public void set(T value) {
Thread t = Thread.currentThread();// 獲取當前執行緒
ThreadLocalMap map = getMap(t);//獲取儲存資料的MAP
if (map != null)//判斷儲存資料的map是否為空,如果為空則建立map,如果不為空,則存入資料
map.set(this, value);
else
createMap(t, value);//如果沒有就建立一個,當前Thread
}
//獲取當前執行緒的值儲存區域
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //執行緒的成員變數
}
//給當前執行緒建立一個新的ThreadLocalMap,並存儲以當前ThreaLocal例項為key的鍵值對。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get方法
-
如果在訪問之前沒有儲存任何資料,則對ThreadLockMap進行初始化,綁定當前執行緒。並進行初始值的建立,和當前ThreadLock例項進行繫結,kv儲存
-
如果獲取到之前儲存的值,則進行返回存入的值;
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//獲取當前執行緒的儲存map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//獲取當前的ThreadLock的節點,具體上面有介紹
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();//如果不存在則進行初始化
}
//進行一些初始化操作
private T setInitialValue() {
T value = initialValue();//ThreaLock提供的一個可繼承的方法,進行初始化操作
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//獲取當前執行緒的
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
remove 方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)//在儲存的map不為空的情況下,移除當前ThreadLock例項
m.remove(this);
}
總結
注意點:
1. ThreadLocalMap儲存的key值為當前的ThreadLock例項,所以一般要求ThreadLocal是單例的
2. 單個執行緒可以有多個ThreadLocal例項,儲存資料。
總結分析:
1.ThreadLocal的設計思路:
-
原理:相當給當前執行緒的建立獨有資料域,方便當前執行緒訪問。這樣避免了多執行緒環境中,訪問當前執行緒鎖的相關問題
-
之所以使用ThreadLocalMap進行儲存,而不用HashMap,上面原始碼分析說了,源於Reference類的特性。
-
弱應用,當物件無用的時候,會快速被GC回收。符合執行緒的特點,一般一個執行緒時間不會很長,當伴隨大量資料時能快速回收
-
當前執行緒結束的時候,要通過呼叫ThreadLocal的remove方法,即使將ThreadLocalMap中的對應例項key設定為空,方便GC快速回收
-
-
ThreadLocal操作更有優勢,下面我具體分析以下
為什麼用ThreadLocal類直接操作儲存資料 ?
1.簡化程式碼:
試想一下上面的原始碼,如果直接從Thread中存放資料,
首先,要用Thread.currentThread()獲取當前執行緒,
然後,再通過當前執行緒thread獲取儲存的Map
再根據key獲取對那個的操作
從上面的原始碼可以看出,這些操作細節,都已經在ThreadLocal類中進行了隱藏;
1.避免每次使用時的重複程式碼;
2.而使用ThreadLocal例項獲取資料,相當於拿key直接獲取資料,隱藏了獲取細節
2.限制和統一管理儲存值
1. 內部的Entry陣列,進行了泛型約束,避免了直接在Thread類中Map操作key的不規範性;
2. 避免了開發人員直接操作當前執行緒,而使用“變數副本”進行操作