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);//獲取存放的位置
/**
* 當i位置剛好已經存儲了數據
*/
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {//向i+1<len遍歷
ThreadLocal<?> k = e.get();//獲取存入的ThreadLock
if (k == key) {//判斷是否一致,如果一致,替換值並返回
e.value = value;
return;
}
if (k == null) {//如果i節點獲取到的ThreadLocal為空的話
replaceStaleEntry(key, value, i);
return;
}
}
//沒有存儲數據的情況
tab[i] = new Entry(key, value);//新增信的節點
int sz = ++size;
/**
* 判斷是不是要進行擴容
* Entry數組的擴容是2倍擴容的;
*/
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// 判斷i之前的非空節點的位置
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
//從staleSlot又向后循環,找到非空節點,查看是否又相同key
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
/**
* 判斷和查詢的實例是否同一個,如果key相同,則和staleSlot節點進行替換位置
*/
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);//這里進行rehash
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// 如果key沒找到那就在staleSlot位置插入一個新節點
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// 判斷是不是要重新盡心hash
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
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值為當前的ThreadLocal實例,所以一般要求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. 避免了開發人員直接操作當前線程,而使用“變量副本”進行操作