文章目錄
- 一.概述
- 二.内部類分析
-
- 1.ThreadLocalMap
-
- A.内部類和參數分析
-
- 1.内部類Entry
- 2.table:Entry數組
- 3.size
- 4.threshold
- 5.INITIAL_CAPACITY
- B.方法分析
-
- 1.連續段清除方法:expungeStaleEntry
- 2.掃描控制清除操作:cleanSomeSlots
- 3.換值操作:replaceStaleEntry
- 4.getEntry
- 5.set
- 6.remove
- 三.方法分析
-
- 1.get
- 2.set
一.概述
ThreadLocal能夠保證線程的隔離,不同的線程取到對應的參數,那麼這邊是怎麼做到的呢,我們來看一下源碼
二.内部類分析
1.ThreadLocalMap
我們可以看到整個代碼結構,包括ThreadLocal的方法基本都是調用這個資料結構,是以我們首要的目标就是分析這個資料結構
A.内部類和參數分析
1.内部類Entry
Entry繼承WeakReference,使用弱引用,可以将ThreadLocal對象的生命周期和線程生命周期解綁,持有對ThreadLocal的弱引用,可以使得ThreadLocal在沒有其他強引用的時候被回收掉,這樣可以避免因為線程得不到銷毀導緻ThreadLocal對象無法被回收。
/**
* Entry繼承WeakReference,并且用ThreadLocal作為key.如果key為null
* (entry.get() == null)表示key不再被引用,表示ThreadLocal對象被回收
* 是以這時候entry也可以從table從清除。
**/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
//Entry的value這個參數就是TreadLocal存儲的元素
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
2.table:Entry數組
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
* 類似HashMap的結構,這個是用來存儲Entry數組.
*/
private Entry[] table;
3.size
/**
* The number of entries in the table.
* 在table中存在多少個數組
*/
private int size = 0;
4.threshold
/**
* The next size value at which to resize.
* 進行擴容的門檻值,表使用量大于它的時候進行擴容。
*/
private int threshold;
//定義為表長度的2/3
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
5.INITIAL_CAPACITY
/**
* The initial capacity -- MUST be a power of two.
* 預設初始化的table大小
*/
private static final int INITIAL_CAPACITY = 16;
B.方法分析
1.連續段清除方法:expungeStaleEntry
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//1.先把傳進來的節點值置為null,使用的size-1
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
Entry e;
int i;
//2.往後掃描一段不為空的Entry.遇到空Entry退出
for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//3.如果目前key為空,則進行置空操作
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
//4.如果key不為空,計算索引.進行rehash的操作
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
/**
* 這邊的rehash操作還真是不知道為什麼這麼做
*/
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
//5.傳回下一個為空的索引
return i;
}
操作步驟如下:
2.掃描控制清除操作:cleanSomeSlots
/**
* 這個方法是用來做掃描控制清除操作,也就是對TreadLocalMap裡面key為空的元素進行全部清除
* @param i 起始位置
* @param n 掃描次數
* @return 是否進行了清除操作
*/
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
//1.目前下标向後掃描,線性探測法,當超過最大下标時傳回
i = nextIndex(i, len);
Entry e = tab[i];
//2.如果遇到這種value不被使用了的Entry
if (e != null && e.get() == null) {
//3.擴大掃描範圍
n = len;
removed = true;
//4.這邊會進行目前Entry的清空.然後繼續往後掃描清除,并傳回第一個為空的下标位置.
i = expungeStaleEntry(i);
}
} while ((n >>>= 1) != 0);
//5.直到循環結束,傳回是否清除的結果
return removed;
}
3.換值操作:replaceStaleEntry
這個方法乍一看上去非常惡心,但是如果你把它分成三塊來看, 就沒那麼惡心了,因為他的目的就是把對應位置的key-value換掉,隻是在這個過程中還加入了了掃描清除的操作.
我們按照每一部分來看這個方法,就簡單了,下面這個圖:
其實這邊就分成了四種大情況,3的位置就是設值的操作,隻要2部分不進入2.2,就一定會達到設值的目的。
那麼這邊除了設定,還有一個重要的操作就是髒Entry的清除操作.1是向前找髒Entry,2是向後找髒Entry.
這麼說的話就是找到沒找到,四種情況,我們一一來分析一下.
- 向前找向後找都沒找到髒Entry
- 向前找到了髒Entry,向後沒找到
- 向前沒找到,向後找到了髒Entry
- 向前向後都找到了髒Entry
/**
*
* @param key 要儲存的key
* @param value 要儲存的value
* @param staleSlot 目前位置(這邊傳入的是目前key=null的位置下标)
*/
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
//1.從目前位置向前搜尋到key=null的entry,并且在空Entry處結束,得到key=null的Entry下标
for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
//2.向後搜尋,直到空Entry結束
for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//3.如果找到key一樣的Entry.
if (k == key) {
//4.進行值的覆寫
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
//5.從目前位置開始進行指定次數的清除操作
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
//6.如果在查找過程中沒有找到可以覆寫的entry,則将新的entry插入在髒entry
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
4.getEntry
getEntry這個方法通過算法得到下标之後,判斷下标對應本地變量參數是否存在,存在則傳回,否則繼續,操作步驟:
private Entry getEntry(ThreadLocal<?> key) {
//1.一種算法稱為完美散列
int i = key.threadLocalHashCode & (table.length - 1);
//2.拿到Entry,不為空且key和拿到的元素相同,傳回Entry
Entry e = table[i];
if (e != null && e.get() == key)
return e;
//3.如果不滿足上述條件則進入這邊
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
//1.當Entry不為空時,循環周遊
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
//2.當key弱應用已經為空時,這邊要進行清除操作,并且這個方法會繼續往後掃描清除,知道null元素傳回
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
//3.沒有結果則傳回null
return null;
}
5.set
set的方法這邊有這樣幾步:
- 向後探測,遇到key相等的,修改key對應的值,return
- 向後探測,遇到key為空的髒Entry,調用替換該位置的值的方法,return
- 向後探測沒有遇到上述兩種情況,在目前位置插入這個Entry
- 控制清除方法遇到髒Entry進行清除并且長度超過,則rehash這個TreadLocalMap
6.remove
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)]) {
//1.循環找到key相同的Entry,進行clear操作
if (e.get() == key) {
e.clear();
//2.從該位置往後進行清除操作.
expungeStaleEntry(i);
return;
}
}
}
三.方法分析
1.get
get方法這邊也是用到了懶性加載的思想,真正使用到的時候才去建立
public T get() {
//1.拿到目前線程
Thread t = Thread.currentThread();
//2.擷取TreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//3.Map不為空,則拿到Entry
ThreadLocalMap.Entry e = map.getEntry(this);
//4.Entry不為空則傳回值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}
//5.ThreadLocalMap或者Entry為空,則初始化Map或Entry
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
//1.拿到目前線程
Thread t = Thread.currentThread();
//2.拿到目前Map
ThreadLocalMap map = getMap(t);
//3.map不為空則設值Entry
if (map != null)
map.set(this, value);
else
//4.map為空則建立新map
createMap(t, value);
//5.傳回值
return value;
}
2.set
set就比較簡單啊,這邊就是用到了懶性加載的思想,當用到的時候才去加載
public void set(T value) {
//1.拿到目前線程
Thread t = Thread.currentThread();
//2.擷取ThreadLocalMap
ThreadLocalMap map = getMap(t);
//3.如果Map不為空,調用ThreadLocalMap的set方法設值
if (map != null)
map.set(this, value);
else
//4.如果Map為空,建構value入參的Map
createMap(t, value);
}