ThreadLocal缺點及解決方案
每個Thread上都有一個threadLocals屬性,它是一個ThreadLocalMap,裡面存放著一個Entry陣列,key是ThreadLocal型別的弱引用,value是對用的值。所有的操作都是基於這個ThreadLocalMap操作的。
但是它有一個侷限性,就是不能在父子執行緒之間傳遞。 即在子執行緒中無法訪問在父執行緒中設定的本地執行緒變數。 後來為了解決這個問題,引入了一個新的類InheritableThreadLocal。
使用該方法後,子執行緒可以訪問在建立子執行緒時父執行緒當時的本地執行緒變數,其實現原理就是在父執行緒建立子執行緒時將父執行緒當前存在的本地執行緒變數複製到子執行緒的本地執行緒變數中。
public class InheritableThreadLocal
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
從上面的結構中可以看出,它主要是重寫了getMap、createMap方法。
Thread類中有兩個重要的變數
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
init()方法片段
Thread parent = currentThread();
.....
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
子執行緒時透過在父執行緒透過呼叫new Thread()方法來建立子執行緒,Thread#init方法在Thread的構造方法中被呼叫。
主要是先獲取當前執行緒物件,即待建立的執行緒的父執行緒
如果父執行緒的inheritableThreadLocals不為空,並且inheritThreadLocals為true(預設為true),則使用父執行緒的ingerit本地變數的值來建立子執行緒的inheritableThreadLocals結構,即將父執行緒中的本地變數複製到子執行緒中。
private Entry[] table;
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++;
}
}
}
}
子執行緒預設複製父執行緒的方式是淺複製,如果需要使用深複製,如果需要使用深複製,需要使用自定義ThreadLocal,繼承InheritThreadLocal並重寫childValue方法。
而它的實現原理主要是在建立子執行緒時將父類執行緒中的本地變數值parent.inheritableThreadLocals複製到子執行緒,即複製的機會在建立子執行緒時。
但是它也有一種缺陷,就是在使用執行緒池的情況下,因為執行緒池是複用執行緒,不會重複建立,而上述的inheritableThreadLocals是在建立子執行緒時才會將父執行緒的值複製到子執行緒,但是線上程池中不會重複建立,所以多次使用後,仍然記錄的是第一次提交任務時的外部執行緒的值,造成了資料的錯誤。
那如何解決這種現象呢?
只需在使用者執行緒向執行緒池提交任務時複製父執行緒的上下文環境,就可以實現本地變數線上程池呼叫的透傳。 基於這個思想,阿里提出了TransmittableThreadLocal類。
在提交任務時的Runable或者Callable必須封裝成TtlRunable或者TtlCallable。TransmittableThreadLocal覆蓋實現了ThreadLocal的set、get、remove,實際儲存inheritableThreadLocal值的工作還是inheritableThreadLocal父類完成,TransmittableThreadLocal只是為每個使用它的Thread單獨記錄一份儲存了哪些TransmittableThreadLocal物件。
public final void set(T value) {
super.set(value);
if (null == value) removeValue();
else addValue();
}
首先它會呼叫父類的InheritableThreadLocal的set方法,將value加入到Thread物件的inheritableThreadLocals變數中。
如果value為null,則呼叫removeValue()方法,否則呼叫addValue方法。
private void addValue() {
if (!holder.get().containsKey(this)) { // @1
holder.get().put(this, null); // WeakHashMap supports null value.
}
}
private void removeValue() {
holder.get().remove(this);
}
呼叫addValue方法,會將當前ThreadLocal儲存到TransmittableThreadLocal的全域性靜態變數hodler。所有和Thread繫結的所有TransmittableThreadLocal物件都儲存在這個holder中,holder只是為了記錄當前Thread繫結了哪些TransmittableThreadLocal物件。
下面具體講一下TtlRunable的原理。
private TtlRunnable(@Nonnull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
this.capturedRef = new AtomicReference