作者: 一字馬胡
轉載標志 【2017-11-03】
更新日志
日期 | 更新內容 | 備注 |
---|---|---|
2017-11-03 | 添加轉載標志 | 持續更新 |
ThreadLocal
ThreadLocal從字面理解就是線程本地變量,貌似是一種線程私有的緩存變量的容器。為了說明ThreadLocal的特點,舉個例子:比如有三個人,每個人比作一個線程,它們都需要一個袋子來裝撿到的東西,也就是每個線程都希望自己有一個容器,當然,自己的撿到的東西肯定不希望和別人分享啊,也就是希望這個容器對其他人(線程)是不可見的,如果現在只有一個袋子,那怎么辦?
- 每個人在撿東西之前一定會先搶到那個唯一的袋子,然后再撿東西,如果使用袋子的時間到了,就會馬上把里面的東西消費掉,然后把袋子放到原來的地方,然后再次去搶袋子。這個方案是使用鎖來避免線程競爭問題的,三個線程需要競爭同一個共享變量。
- 我們假設現在不是只有一個袋子了,而是有三個袋子,那么就可以給每個人安排一個袋子,然后每個人的袋子里面的對象是對其他人不可見的,這樣的好處是解決了多個人競爭同一個袋子的問題。這個方案就是使用ThreadLocal來避免不必要的線程競爭的。
大概了解了ThreadLocal,下面來看看它的使用方法:
private static class UnsafeThreadClass {
private int i;
UnsafeThreadClass(int i) {
this.i = i;
}
int getAndIncrement() {
return ++ i;
}
@Override
public String toString() {
return "[" + Thread.currentThread().getName() + "]" + i;
}
}
private static ThreadLocal<UnsafeThreadClass> threadLocal = new ThreadLocal<>();
static class ThreadLocalRunner extends Thread {
@Override
public void run() {
UnsafeThreadClass unsafeThreadClass = threadLocal.get();
if (unsafeThreadClass == null) {
unsafeThreadClass = new UnsafeThreadClass(0);
threadLocal.set(unsafeThreadClass);
}
unsafeThreadClass.getAndIncrement();
System.out.println(unsafeThreadClass);
}
}
上面的例子僅僅是為了說明ThreadLocal可以為每個線程保存一個本地變量,這個變量不會受到其他線程的干擾,你可以使用多個ThreadLocal來讓線程保存多個變量,下面我們分析一下ThreadLocal的具體實現細節,首先展示了ThreadLocal提供的一些方法,我們重點關注的是get、set、remove方法。
首先,我們需要new一個ThreadLocal對象,那么ThreadLocal的構造函數做了什么呢?
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
很遺憾它什么都沒做,那么初始化的過程勢必是在首次set的時候做的,我們來看一下set方法的細節:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
看起來首先根據當前線程獲取到了一個ThreadLocalMap,getMap方法是做了什么?
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
非常的簡潔,是和Thread與生俱來的,我們看一下Thread中的相關定義:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
關于inheritableThreadLocals將在下一小節再學習總結。
獲得了線程的ThreadLocalMap之后,如果不為null,說明不是首次set,直接set就可以了,注意key是this,也就是當前的ThreadLocal啊不是Thread。如果為空呢?說明還沒有初始化,那么就需要執行createMap這個方法:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
沒什么特別的,就是初始化線程的threadLocals,然后設定key-value。
下面分析一下get的邏輯:
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();
}
和set一樣,首先根據當前線程獲取ThreadLocalMap,然后判斷是否為null,如果為null,說明ThreadLocalMap還沒有被初始化啊,那么就返回方法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;
}
protected T initialValue() {
return null;
}
最后會返回null,但是會做一些初始化的工作,和set一樣。在get里面,如果返回的ThreadLocalMap不為null,則說明ThreadLocalMap已經被初始化了,那么就可以正常根據ThreadLocal作為key獲取了。
當線程退出時,會清理ThreadLocal,可以看下面的代碼:
/**
* This method is called by the system to give a Thread
* a chance to clean up before it actually exits.
*/
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
這里做了大量“Help GC”的工作。包括我們本節所講的threadLocals和下一小節要講的inheritableThreadLocals都會被清理。
如果我們想要顯示的清理ThreadLocal,可以使用remove方法:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
邏輯較為直接,很好理解。
InheritableThreadLocal
ThreadLocal固然很好,但是子線程并不能取到父線程的ThreadLocal的變量,比如下面的代碼:
private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
private static InheritableThreadLocal<Integer> inheritableThreadLocal =
new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
integerThreadLocal.set(1001); // father
inheritableThreadLocal.set(1002); // father
new Thread(() -> System.out.println(Thread.currentThread().getName() + ":"
+ integerThreadLocal.get() + "/"
+ inheritableThreadLocal.get())).start();
}
//output:
Thread-0:null/1002
使用ThreadLocal不能繼承父線程的ThreadLocal的內容,而使用InheritableThreadLocal時可以做到的,這就可以很好的在父子線程之間傳遞數據了。下面我們分析一下InheritableThreadLocal的實現細節,下面展示了InheritableThreadLocal提供的方法:
InheritableThreadLocal繼承了ThreadLocal,然后重寫了上面三個方法,所以除了上面三個方法之外,其他所有對InheritableThreadLocal的調用都是對ThreadLocal的調用,沒有什么特別的。我們上文中提到了Thread類,里面有我們本文關心的兩個成員,我們來看一下再Thread中做了哪些工作,我們跟蹤一下new一個Thread的調用路徑:
new Thread()
init(ThreadGroup g, Runnable target, String name, long stackSize)
init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals)
->
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
createInheritedMap(ThreadLocalMap parentMap)
ThreadLocalMap(ThreadLocalMap parentMap)
上面列出了最為關鍵的代碼,可以看到,最后會調用ThreadLocal的createInheritedMap方法,而該方法會新建一個ThreadLocalMap,看一下構造函數的內容:
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++;
}
}
}
}
parentMap就是父線程的ThreadLocalMap,這個構造函數的意思大概就是將父線程的ThreadLocalMap復制到自己的ThreadLocalMap里面來,這樣我們就可以使用InheritableThreadLocal訪問到父線程中的變量了。
對ThreadLocal更為具體和深入的分析將在其他的篇章中進行,本文點到即可,為了深入理解ThreadLocal,可以閱讀ThreadLocalMap的源碼,以及可以在項目中多思考是否可以使用ThreadLocal來做一些事情,比如,如果我們具有這樣一種線程模型,一個任務從始至終只會被一個線程執行,那么可以使用ThreadLocal來計算運行該任務的時間。