前言
剛看過EventBus和AndroidEventBus的源碼, 發現里面都有用到ThreadLocal, 那ThreadLocal到底為何物呢, 相信從事Java并發編程的朋友們并不陌生, 請跳過本篇文章. 下面講一下我自己的理解, 如有不對的地方, 歡迎指正.
對ThreadLocal的理解
看源碼注釋: ThreadLocal維護線程的本地變量, 這些變量不同于線程中的普通副本, 它可以通過get, set方法獲取和操作線程中初始變量的獨立副本. ThreadLocal實例一般作為類中的私有靜態屬性來關聯一個線程的狀態(例如用戶id, 或事件id).
簡單來說就是通過ThreadLocal維護一個變量, 然后不同的線程來訪問這個變量的時候, ThreadLocal會為每一個線程提供一個獨立的變量副本, 這樣不同的線程就可以互不影響的操作這個變量. 所以ThreadLocal普遍應用于多線程開發.
看到這里還不了解ThreadLocal維護變量到底有什么用? 別急, 看一個簡單的例子就知道了.
public class JavaTest {
private static ThreadLocal<String> localString = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "Hello World!";
}
};
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + localString.get());
localString.set("aaa");
System.out.println(Thread.currentThread().getName() + ":" + localString.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + localString.get());
localString.set("bbb");
System.out.println(Thread.currentThread().getName() + ":" + localString.get());
}
}).start();
}
}
打印結果:
Thread-0:Hello World!
Thread-0:aaa
Thread-1:Hello World!
Thread-1:bbb
看到沒, 線程0對ThreadLocal維護的變量修改之后, 線程1修改之前獲取到的值還是初始值, 而不是被線程0修改之后的值. 如果是普通變量, 線程1獲取到的肯定是被線程0修改之后的值.
ThreadLocal的基本使用
ThreadLocal支持泛型, 使用起來非常簡單, 有以下幾個操作:
- public void set(T value): 為當前線程的本地線程變量賦值.
- public T get(): 取出當前線程的本地線程變量.
- protected T initialValue(): 返回當前線程的本地線程變量的初始化值, 是protected 類型, 實例化時可以重寫該方法為變量初始化, 默認為null.
- public void remove(): 刪除當前線程的本地線程變量.
ThreadLocal源碼解析
ThreadLocal到底是怎么為每一個線程提供獨立的變量副本的呢, 看下Thread的代碼, 你會發現有一個屬性ThreadLocal.ThreadLocalMap threadLocals, 在整個Thread代碼中threadLocals也沒做什么操作.
class Thread implements Runnable {
...
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
//threadLocals 由ThreadLocal來維護
ThreadLocal.ThreadLocalMap threadLocals = null;
...
//線程結束之前通知系統來清理
private void exit() {
...
/* Speed the release of some of these resources */
threadLocals = null;
...
}
來看下ThreadLocal.ThreadLocalMap的廬山真面目:
public class ThreadLocal<T> {
...
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
...
}
}
ThreadLocalMap是ThreadLocal的一個靜態內部類, 是用來維護線程本地變量的一個自定義HashMap, 而ThreadLocal操作數據也都是通過ThreadLocalMap來實現的, 下面再具體分析.
可以看到ThreadLocalMap內部又有一個靜態類Entry, 是對ThreadLocal的弱引用, 保存了ThreadLocal對象和變量值, 可以理解為key是ThreadLocal對象,值是變量值.
Thread, ThreadLocal, ThreadLocalMap之間到底是怎么聯系到一起的, 后面會揭曉.
來看ThreadLocal對數據的操作代碼:
- set方法
public void set(T value) {
//獲取當前線程
Thread t = Thread.currentThread();
//獲取當前線程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);//map不為空的話, 保存當前變量值
else
createMap(t, value);//map為空的話創建一個map
}
ThreadLocalMap getMap(Thread t) {
//返回對應線程的threadLocals屬性
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
//創建ThreadLocalMap
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
看到沒, set的時候會獲取當前線程的ThreadLocalMap, 然后通過該map存數據, key是當前ThreadLocal對象, value是要設置的變量.
繼續看ThreadLocalMap中創建及set方法:
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
//創建一個長度為16的Entry數組
table = new Entry[INITIAL_CAPACITY];
//根據傳進來firstKey的hashCode值獲取一個下標
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//創建一個Entry,保存firstKey和初始值, 存入table
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;
//根據參數ThreadLocal 的hashCode獲取下標
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
//獲取Entry中的key
ThreadLocal k = e.get();
//key是當前設置的ThreadLocal對象, 就把值保存進去
if (k == key) {
e.value = value;
return;
}
//如果Entry不為空,但其key為空
if (k == null) {
//從table數組中找出key是當前ThreadLocal對象的Entry, 存入變量, 放到當前位置, 如果沒找到, 就new一個Entry放入當前位置
replaceStaleEntry(key, value, i);
return;
}
}
//如果通過下標找到的Entry都為空,就new一個Entry, 保存ThreadLocal和變量,存入數組
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
總結一下set方法, 就是從當前線程的map中取出引用是當前ThreadLocal對象的Entry, 沒有的話創建一個Entry, 保存當前變量. map為空的話就去創建一個map, 然后新建一個Entry, 鍵是當前ThreadLocal對象, 值是變量, 保存在Entry數組中.
- get方法
public T get() {
Thread t = Thread.currentThread();
//獲取當前線程中的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//如果map不為空, 獲取map中的Entry對象, 獲取value
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
//如果map為空, 就從初始值來獲取
return setInitialValue();
}
先來看下setInitialValue方法:
private T setInitialValue() {
//通過initialValue獲取初始值, 不重寫該方法的話, 默認為null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
setInitialValue是不是跟set方法很像呢, 先從initialValue獲取初始值, 再set保存. 下面看下通過ThreadLocalMap.Entry獲取變量值的方法:
private Entry getEntry(ThreadLocal key) {
//根據key的hashCode值獲取下標
int i = key.threadLocalHashCode & (table.length - 1);
//根據下標找到數組中對應的Entry
Entry e = table[i];
//如果Entry的鍵與傳進來的ThreadLocal 相等, 就找到了對應的Entry
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) {
獲取Entry的引用
ThreadLocal k = e.get();
//是當前key, 找到, 返回
if (k == key)
return e;
//如果引用為空, 從數組table中刪除該位置的Entry
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);//下標加一, 繼續尋找
e = tab[i];
}
//如果整個數組中都沒找到對應ThreadLocal 的Entry, 返回null
return null;
}
總結一下get方法就是從當前線程的map中找到引用是當前ThreadLocal對象的Entry, 獲取保存在Entry中的變量值, 沒有的話獲取變量的初始值并保存在map中.
- remove方法
public void remove() {
//獲取當前線程的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private void remove(ThreadLocal key) {
Entry[] tab = table;
int len = tab.length;
//根據ThreadLocal的hashCode找到下標
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//循環查找數組中引用等于要移除的ThreadLocal的Entry
if (e.get() == key) {
e.clear();
//從數組中刪除該位置的Entry
expungeStaleEntry(i);
return;
}
}
}
就是獲取當前線程的map, 然后從數組中刪除key是當前ThreadLocal的Entry.
到此, ThreadLocal的代碼基本都已經看完了, Thread, ThreadLocal, ThreadLocalMap之間的聯系你看明白了嗎?其實通過ThreadLocal來操作數據, 本質是通過當前Thread中的ThreadLocalMap來操作數據. 而變量的源頭是通過initialValue設置初始值, 所以不同線程都有一份變量的副本, 可以互不影響的操作這個變量.