1.ThreadLocal作用
- 作用:爲變量在線程中都創建副本,線程可訪問自己內部的副本變量。該類提供了線程局部 (thread-local) 變量,訪問這個變量(通過其 get 或 set 方法)的每個線程都有自己的局部變量,它獨立於變量的初始化副本
- 原理:每個線程都有一個ThreadLocalMap類型變量 threadLocals。ThreadLocal的set()會在threadLocals中保存以ThreadLocal對象爲key,以保存的變量爲value的值,get()會獲取該值
2.ThreadLocal繼承關係
3.源碼走讀
3.1.ThreadLocal.java
public class ThreadLocal<T> {
//**每一個實例都有一個唯一的threadLocalHashCode,值爲上一個實例的值加上0x61c88647
//**作用是爲了讓哈希碼能均勻的分佈在2的N次方的數組裏
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
//**返回此線程局部變量的當前線程的“初始值”
//**線程第一次使用get()方法時調用此方法,如果線程之前調用了set(T)方法,則不會對該線程再調用該方法
//**通常,此方法對每個線程最多調用一次,但調用了remove(),則會再次調用此方法
//**默認返回null,如果希望返回其它值,則須創建子類,並重寫此方法,通常將使用匿名內部類完成此操作
protected T initialValue() {
return null;
}
//**在java8,使用函數式編程的方式設置並返回當前線程變量的初始值,與上個方法功能相同
//**示例:ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "test");
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
//**返回SuppliedThreadLocal對象,SuppliedThreadLocal是ThreadLocal的子類,
//**重寫的initialValue方法調用supplier的get方法做爲當前線程變量的初始值
return new SuppliedThreadLocal<>(supplier);
}
//**返回此線程局部變量的當前線程副本中的值,如果變量沒有用於當前線程的值,則返回initialValue()的值
public T get() {
//**獲取當前線程的實例
Thread t = Thread.currentThread();
//**獲取當前線程中的ThreadLocalMap類型變量threadLocals
ThreadLocalMap map = getMap(t);
if (map != null) {
//**從threadLocals中獲取以this爲key的Entry對象
ThreadLocalMap.Entry e = map.getEntry(this);
//**如果Entry對象不爲空,則返回它的value
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//**如果threadLocals對象不爲空或者Entry爲空,則調用setInitialValue進行初始化
return setInitialValue();
}
//**使用initialValue()的值初始化線程局部變量
private T setInitialValue() {
//**獲取線程局部變量的初始值,默認爲null
T value = initialValue();
//**獲取當前線程的實例
Thread t = Thread.currentThread();
//**獲取當前線程中的ThreadLocalMap類型變量threadLocals
ThreadLocalMap map = getMap(t);
//**如果threadLocals不爲空,設置以this爲key,以value爲值的Entry對象
if (map != null)
map.set(this, value);
//**如果threadLocals爲空,則進行初始化,並設置以this爲key,以value爲值的Entry對象
else
createMap(t, value);
return value;
}
//**設置線程局部變量的值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//**移除此線程局部變量當前線程的值,如果隨後調用get()方法,且沒有調用set()設置值,則將調用initialValue()重新初始化值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
//**從線程實例中獲取threadLocals對象
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//**初始化線程t的threadLocals對象,並設置以this爲key,以firstValue爲值的Entry對象
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//**根據主線程中的ThreadLocalMap對象創建子線程的ThreadLocalMap對象
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
//**ThreadLocal對象不支持,在InheritableThreadLocal中實現
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
}
3.2.SuppliedThreadLocal.java
- SuppliedThreadLocal是ThreadLoacl的靜態內部類,ThreadLocal的withInitial方法使用Supplier對象創建SuppliedThreadLocal對象
- 作用是爲了在java8,支持使用函數式編程的方式設置並返回當前線程變量的初始值
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
//**重寫的initialValue方法,調用supplier的get方法做爲當前線程變量的初始值
protected T initialValue() {
return supplier.get();
}
}
3.3.InheritableThreadLocal.java
- 作用:把父線程變量的值傳遞到子線程中。可通過重寫childValue方法,改變從父線程中獲取的值
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
//**父線程inheritableThreadLocals的值傳給子線程的inheritableThreadLocals的處理邏輯
protected T childValue(T parentValue) {
return parentValue;
}
//**重寫父類的方法,維護inheritableThreadLocals變量
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
//**重寫父類的方法,維護inheritableThreadLocals變量
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
- 原理和解析:
- 每個線程都還有另外一個ThreadLocalMap類型變量inheritableThreadLocals
- InheritableThreadLocal重寫了getMap和createMap方法,維護的不在是threadLocals,而是inheritableThreadLocals
- 當主線程創建一個子線程的時候,會判斷主線程的inheritableThreadLocals是否爲空
- 如果不爲空,則會把inheritableThreadLocals的值傳給子線程的inheritableThreadLocals,傳送的邏輯是childValue實現的
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
...
//**獲取主線程的實例
Thread parent = currentThread();
...
//**如果主線的inheritableThreadLocals不爲空
if (parent.inheritableThreadLocals != null)
//**根據主線程的inheritableThreadLocals創建子線程的inheritableThreadLocals
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}
- 注意:
- 因爲傳送邏輯是在創建子線程的時候完成的,子線程創建後,主線程在修改InheritableThreadLocal變量的值,是無法傳給子線程的
- 創建子線程完成後,原則上子線程和父線程中InheritableThreadLocal變量的值在沒有關聯,各自調用set/get/remove都隻影響本線程中的值
- 如果InheritableThreadLocal變量的值是引用類型,通過get方法獲取到對象後,直接修改了該對象的屬性,則父線程和子線程都會受影響
public class Test {
private static List getList(String param) {
List rst = new ArrayList<>();
rst.add(param);
return rst;
}
private static final InheritableThreadLocal<List> threadLocal = new InheritableThreadLocal<>();
public static void test(Consumer<InheritableThreadLocal<List>> consumer) throws InterruptedException {
threadLocal.set(getList("test"));
Thread child = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()){
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("子線程中threadLocal的值:" + threadLocal.get());
}
});
System.out.println("主線程中threadLocal的值:" + threadLocal.get());
child.start();
TimeUnit.MILLISECONDS.sleep(1);
consumer.accept(threadLocal);
System.out.println("主線程中threadLocal的值:" + threadLocal.get());
TimeUnit.MILLISECONDS.sleep(3);
child.interrupt();
}
public static void main(String[] args) throws InterruptedException {
//**創建子線程完成後,主線程調用set方法修改值,不會影響到子線程
test(local -> local.set(getList("test1")));
System.out.println("===========================");
//**保存list對象時,通過get方法獲取,然後修改list的值,則會影響到子線程
test(local -> local.get().set(0, "test2"));
}
}
//**執行結果
主線程中threadLocal的值:[test]
子線程中threadLocal的值:[test]
主線程中threadLocal的值:[test1]
子線程中threadLocal的值:[test]
===========================
主線程中threadLocal的值:[test]
子線程中threadLocal的值:[test]
主線程中threadLocal的值:[test2]
子線程中threadLocal的值:[test2]
3.4.ThreadLocalMap.java
- 作用:ThreadLocalMap是ThreadLocal的內部類。存放以ThreadLocal變量爲key,以保存的變量爲value的鍵值對
- 原理:
- ThreadLocalMap內部以Entry[]做爲存儲,原始長度默認爲16,當元素個數達到擴容閥值(數組長度的3/4)-擴容閥值/4,則自動擴容,擴容到上次長度的2倍。Entry[]的長度必須是2的倍數
- Entry[]存儲元素並不是按索引順序存儲,而是根據ThreadLocal進行計算存儲位置,這樣能實現根據ThreadLocal都能快速定位鍵值對,而不用遍歷數組的每個元素
- 計算方法:ThreadLocal.threadLocalHashCode & (Entry[].length - 1)計算,ThreadLocal每一個實例都有一個唯一的threadLocalHashCode,值爲上一個實例的值加上0x61c88647,該算法可以生成均勻的分佈在2的N次方數組裏的下標
- 如果計算的存儲位置已經有元素,則會存放到下一個索引的位置,ThreadLocalMap會清理過期數據,並重新根據計算的存儲位置重置,以保證儘可能減少和糾正此類問題
static class ThreadLocalMap {
//**存放單個鍵值對的對象
//**弱引用: 如果某個對象只有弱引用,那麼gc會立即回收
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//**默認初始化的大小,必須是2的倍數
private static final int INITIAL_CAPACITY = 16;
//**真正存儲數據的數組,長度必須是2的倍數
private Entry[] table;
//**ThreadLocalMap的大小,即上述Entry[]中存放元素的個數
private int size = 0;
//**自動擴容的閥值
private int threshold; // Default to 0
//**設置自動擴容的閥值,爲設定長度的2/3
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
//**下一個索引
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
//**上一個索引
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
//**創建ThreadLocalMap,並設置第一個鍵值對
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//**根據默認初始化的大小初始化Entry[]
table = new Entry[INITIAL_CAPACITY];
//**根據threadlocal對象的threadLocalHashCode和Entry[]數組的長度計算存放的位置
//**該算法可以生成均勻的分佈在2的N次方數組裏的下標
//**每個鍵值對並不是按順序存放Entry[]裏面
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//**把Entry對象放到指定位置
table[i] = new Entry(firstKey, firstValue);
//**設置ThreadLocalMap的大小,即Entry[]中存放元素的個數
size = 1;
//**設置自動擴容的閥值
setThreshold(INITIAL_CAPACITY);
}
//**根據parentMap創建另一個parentMap,使用InheritableThreadLocal時,創建子線程時會調用
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) {
//**調用InheritableThreadLocal的childValue方法處理保存的對象
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++;
}
}
}
}
//**根據threadlocal獲取Entry對象
private Entry getEntry(ThreadLocal<?> key) {
//**計算下標
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//**如果對象存在,且key一樣,則返回
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;
//**從指定索引開始遍歷,直到數據爲null
while (e != null) {
ThreadLocal<?> k = e.get();
//**如果數據存在則返回
if (k == key)
return e;
//**threadlocal對象爲空,刪除過期數據
if (k == null)
//**刪除過期數據
expungeStaleEntry(i);
//**i爲下一個索引
else
i = nextIndex(i, len);
//**e爲下一個索引的值
e = tab[i];
}
//**沒有數據不存在則返回null
return null;
}
//**根據threadlocal對象設置value
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//**計算存放的索引
int i = key.threadLocalHashCode & (len-1);
//**從指定索引開始遍歷Entry[],直到數據爲null
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//**如果數據存在,則直接返回
if (k == key) {
e.value = value;
return;
}
//**如果key爲空,則替換當前索引的數據,並返回
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//**設置指定索引的數據
tab[i] = new Entry(key, value);
int sz = ++size;
//**如果沒有數據需要清理並且數組長度大於了擴容閥值,則擴容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
//**根據key刪除數據
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
//**計算存放的索引
int i = key.threadLocalHashCode & (len-1);
//**從指定的索引開始遍歷Entry[],直到數據爲null
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
//**如果指定key存在,則刪除指定數據
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
//**替換指定索引的過期數據的
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
//**從指定索引往前找,找到過期數據的索引
int slotToExpunge = staleSlot;
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();
//**如果是數據的key等於指定的key
if (k == key) {
//**替換它的value
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);
}
//**刪除指定索引的過期數據,並返回數據爲null的索引
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//**指定索引的數據置爲null,數據減一(刪除指定數據)
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();
//**如果key爲空,Entry置爲空,數據減一(刪除指定數據)
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
//**重新計算存放的索引
int h = k.threadLocalHashCode & (len - 1);
//**如果新索引不等於原索引,則原索引數據置爲null
if (h != i) {
tab[i] = null;
//**如果新的存放的索引有數據,則存放到新索引的下一個索引,直到沒有數據爲止
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
//**返回數據爲null的索引
return i;
}
//**從指定索引開始清理數據
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
//**刪除過期數據並擴容
private void rehash() {
//**刪除所有的過期數據
expungeStaleEntries();
//**數據量 >= 擴容閥值 - 擴容閥值 / 4,則擴容
if (size >= threshold - threshold / 4)
resize();
}
//**擴容
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
//**擴容爲原來的2倍
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
//**把舊數據存放在新的Entry[]中
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;
}
//**刪除所有的過期數據
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
//**如果Entry不爲空並且key爲空(threadlocal對象爲null)則爲過期數據
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
}