ThreadLocal<T> (1)
ThreadLocal<T>
概述
- 此類提供執行緒區域性變數
- 每個執行緒都有自己的、獨立初始化的變數副本
- ThreadLocal例項通常是類中的私有靜態欄位
- 在一個執行緒消失後,它的所有執行緒本地例項的副本都將被垃圾收集(除非存在對這些副本的其他引用)
類文件例項程式碼
public class ThreadId { private static final AtomicInteger nextId = new AtomicInteger(0); private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; public static int get() { return threadId.get(); } }
使用方式
部分原始碼
靜態常量
private static AtomicInteger nextHashCode = new AtomicInteger()
private static final int HASH_INCREMENT = 0x61c88647
成員變數
private final int threadLocalHashCode = nextHashCode()
方法
ThreadLocal
提供的方法基本都是通過獲取當前執行緒Thread的
成員變數ThreadLocal.ThreadLocalMap來進行操作
get() 獲取此執行緒區域性變數的當前執行緒副本中的值,如果該變數對於當前執行緒沒有值,則首先將其初始化為呼叫initialValue方法返回的值
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
setInitialValue() 設定初始值
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
set() 將此執行緒區域性變數的當前執行緒副本設定為指定值。大多數子類不需要覆蓋此方法,僅依賴initialValue方法來設定執行緒區域性變數的值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
remove()
刪除此執行緒區域性變數的當前執行緒值。
如果此執行緒區域性變數隨後被當前執行緒讀取,則其值將通過呼叫其initialValue方法重新初始化,除非其值在此期間由當前執行緒設定。
這可能會導致在當前執行緒中多次呼叫initialValue方法。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
getMap() 和 createMap()
建立和獲取ThreadLocalMap
// 從這可以看出ThreadLocalMap其實就是Thread類的threadLocals變數
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 建立ThreadLocalMap 並加入初始值
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
建立繼承執行緒本地對映的工廠方法。設計為僅從 Thread 建構函式呼叫
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
ThreadLocalMap
1.基於陣列實現
2.Entry的key(ThreadLocal)採用WeakReference實現
3.計算Entry下標根據key(ThreadLocal)的threadLocalHashCode計算(ThreadLocal實列化時計算threadLocalHashCode)
4.下標衝突後直接順序(i+1)到下一個位置直到查詢到空位(線性探測法)
5.size超過陣列長度2/3時擴容
構造方法
ThreadLocalMaps 是惰性構造的,只在至少有一個條目要放入其中時才建立
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
構造一個新對映,包括來自給定父對映的所有可繼承 ThreadLocal
僅由 createInheritedMap 呼叫
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
ThreadLocalMap.Entry
ThreadLocalMap使用 Entry[]儲存資料,Entry繼承WeakReference
意味著ThreadLocal如果沒有別的引用,則會在下一次GC中被清除
導致Entry[null,val]的出現,ThreadLocalMap提供expungeStaleEntry()方法(get,set.remove過程呼叫)
檢測並清除這種型別的Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
get() set() remove()
根據key(也就是ThreadLocal)獲取Entry
通過ThreadLocal的threadLocalHashCode計算出所在陣列下標
get()
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
// 處理直接找不到的情況
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
// 找到直接返回
if (k == key)
return e;
// 發現了空key則需要刪除
if (k == null)
expungeStaleEntry(i);
// 計算下一個索引(其實就是i+1,到頭就成0)
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
set()
replaceStaleEntry()
原陣列:[entry,null,entry,entry,entry,null,entry...]
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 根據key計算所在index
int i = key.threadLocalHashCode & (len-1);
// 第一部分:之前該index已存在Entry
//
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 找到了,直接替換val並return
if (k == key) {
e.value = value;
return;
}
// 若是找到了一個StaleEntry
if (k == null) {
// 執行到這說明這個entry是StaleEntry,需要替換
replaceStaleEntry(key, value, i);
return;
}
}
// 執行到這代表之前沒有這個key(ThreadLocal),例項化一個
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
// 這個方法處理了staleSlot下標左右最近的空Entry之間的所有Entry
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
// 找到起始的空key下標
// prevIndex獲取上一個index(其實就是i-1,到頭就是len-1)
for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
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;
}
}
}
expungeStaleEntry() 刪除staleSlot(key=null)的Entry
並檢測之後(到下一個null Entry為止)的Entry key=null的情況並處理
原陣列:[entry,null,entry,entry,entry,null,entry...]
刪除下標=2的entry後
下標3,4的entry也會被檢查:
是staleSlot的話則刪除
不是的話重新判斷位置下標是否正確(因為插入的時候有衝突是順著陣列往下找空位)
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 第一部分:直接刪除下標上的entry,先刪val,再entry
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
Entry e;
int i;
// 第二部分:往下繼續找,直到找到的Entry=null
for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 此entry是staleSlot(key=null的),移除資料
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// 有資料的情況
// 因為刪除了第一個元素,可能計算出來的下標會改變
// 下標改變的話重新放到正確位置()
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
擴容相關
// 通過這個方法檢測擴容
private void rehash() {
// 擴容前先刪除所有 key=null 的Entry
expungeStaleEntries();// 1
// 擴容條件: 放寬了一點
// 官方:使用較低的閾值進行翻倍,以避免出現滯後現象
if (size >= threshold - threshold / 4)
resize(); // 2
}
// 1 刪除所有key=null的entry
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
// 2 擴容-翻倍
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}