ThreadLocal
在Android開(kāi)發(fā)中发侵,Handler消息處理機(jī)制中用到了ThreadLocal類(lèi)锨络,花了點(diǎn)時(shí)間對(duì)它進(jìn)行解析绢片。
Thread類(lèi)中有個(gè)成員變量threadLocals,類(lèi)型為T(mén)hreadLocal.ThreadLocalMap乔外,是ThreadLocal自定義的一個(gè)hashmap横媚,它的key是ThreadLocal<?>類(lèi)型纠炮,value是Object。如下:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
注意:在Thread中threadLocals參數(shù)并沒(méi)有被賦值灯蝴,所以默認(rèn)為null
ThreadLocal類(lèi)中恢口,有這樣一個(gè)函數(shù)
//初始化值
protected T initialValue() {
return null;
}
這個(gè)函數(shù)用來(lái)初始化在一個(gè)線(xiàn)程中的初始值,默認(rèn)返回null穷躁,建議使用ThreadLocal時(shí)重寫(xiě)耕肩。
再看看這個(gè)函數(shù)
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
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;
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
從setInitialValue()方法名可以看出是設(shè)置ThreadLocal的初始值,先是獲取當(dāng)前線(xiàn)程 t 问潭,然后通過(guò)getMap(t)方法獲取當(dāng)前線(xiàn)程 t 的threadLocals變量猿诸,就是一個(gè)ThreadLocalMap實(shí)例,通過(guò)上面的getMap(Thread t)方法看出返回的map應(yīng)該是null狡忙,所以執(zhí)行createMap(t,value)方法梳虽。
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
這個(gè)方法很關(guān)鍵,通過(guò)createMap()方法使Thread的成員變量threadLocals賦值了灾茁,并把this當(dāng)做key窜觉,把通過(guò)initialValue()方法返回的值作為value(所以重寫(xiě)initialValue()的話(huà)就避免了初始化值為null的尷尬)。
同時(shí)我們發(fā)現(xiàn)在ThreadLocal類(lèi)中的set()方法也調(diào)用了createMap()方法北专,是不是有種頓時(shí)豁然開(kāi)朗的感覺(jué)禀挫。
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
我們先看看哪里調(diào)用了setInitialValue()法
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
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();
}
發(fā)現(xiàn)整個(gè)ThreadLocal類(lèi)中只有這一個(gè)地方調(diào)用了這個(gè)方法⊥赝牵看了這里就應(yīng)該明白了吧语婴。
- 在沒(méi)有調(diào)用set()方法的情況下, 如果第一次調(diào)用get()方法,getMap()肯定返回一個(gè)null的ThreadLocalMap對(duì)象腻格,就會(huì)執(zhí)行 return setInitialValue(); 語(yǔ)句画拾,返回初始化的值。
- 如果調(diào)用了set()方法的情況下菜职,可以看出set方法也調(diào)用了createMap(t, value);來(lái)給Thread的成員變量threadLocals賦值,那么getMap()肯定就返回一個(gè)不為null的ThreadLocalMap對(duì)象旗闽,把this作為對(duì)象來(lái)獲取value酬核。
小結(jié)
- Thead中有一個(gè)類(lèi)型為T(mén)hreadLocalMap的成員變量threadLocals,并且初始值為null
- ThreadLocalMap是一個(gè)ThreadLocal自定義的HashMap适室,鍵為T(mén)hreadLocal<?>嫡意,值為Object
- ThreadLocal默認(rèn)會(huì)有一個(gè)初始值null,你可以通過(guò)重寫(xiě)initialValue()方法或者調(diào)用set()方法來(lái)改變這個(gè)初始值俱两,他們的本質(zhì)都是去調(diào)用createMap()方法給當(dāng)前的線(xiàn)程Thread的成員變量threadLocals賦值跃洛。
- ThreadLocal的set()方法通過(guò)獲取當(dāng)前線(xiàn)程 t 厅各,通過(guò) t 的成員變量threadLocals的set()方法來(lái)去保存value值,并把當(dāng)前對(duì)象this作為key旧巾。
- 調(diào)用ThreadLocal的get()方法來(lái)獲取value,實(shí)質(zhì)就是獲取當(dāng)前線(xiàn)程t的成員變量threadLocals忍些,并且把自身作為key來(lái)獲取value的過(guò)程鲁猩。
每個(gè)Thread都維護(hù)了一個(gè)ThreadLocalMap對(duì)象,也就是threadLocals變量罢坝,通過(guò)它就可以保存各種不同類(lèi)型的ThreadLocal和對(duì)應(yīng)的值
Demo示例:
public class ThreadStudy {
private static OneThread oneThread;
private static TwoThread twoThread;
private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 99;
}
};
private static ThreadLocal<String> stringThreadLocal = new InheritableThreadLocal<String>(){
@Override
protected String initialValue() {
return "Hello world";
}
};
public static void main(String[] args) throws ExecutionException, InterruptedException {
oneThread = new ThreadStudy().new OneThread();
twoThread = new ThreadStudy().new TwoThread();
oneThread.start();
twoThread.start();
}
class OneThread extends Thread{
public OneThread(){
}
@Override
public void run() {
//給Thread的threadLocals變量賦值廓握,并把stringThreadLocal作為key,"設(shè)置value值"作為value保存在threadLocals里嘁酿。
stringThreadLocal.set("設(shè)置value值");
System.out.println(Thread.currentThread().getName() + " " + stringThreadLocal.get());
}
}
class TwoThread extends Thread{
public TwoThread(){
}
@Override
public void run() {
//由于沒(méi)有調(diào)用set方法隙券,所以會(huì)在第一次調(diào)用get()方法中去個(gè)給Thread的threadLocals變量賦值
System.out.println(Thread.currentThread().getName() + " " + integerThreadLocal.get());
System.out.println(Thread.currentThread().getName() + " " + stringThreadLocal.get());
}
}
}
輸出結(jié)果:
Thread-0 設(shè)置value值
Thread-1 99
Thread-1 Hello world
相信看到這里對(duì)ThreadLocal都理解了吧。
話(huà)說(shuō)ThreadLocalMap類(lèi)到底怎么工作的呢闹司,下面我們一起來(lái)看看娱仔。
ThreadLocalMap
在ThreadLocalMap中有個(gè)靜態(tài)內(nèi)部類(lèi)Entry
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry繼承了WeakReference(弱引用)類(lèi),應(yīng)該這里準(zhǔn)確的說(shuō)是ThreadLocal作為Entry的key并成了弱引用开仰。
其實(shí)在HashMap中也有這樣的一個(gè)同名的接口類(lèi)拟枚,關(guān)系是:HashMap<K,V> 繼承了AbstractMap<K,V> ,然后AbstractMap<K,V> 實(shí)現(xiàn)了 Map<K,V>众弓,在Map<K,V>接口中就有了Entry<K,V>接口恩溅。
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
ThreadLocalMap和HashMap一樣默認(rèn)容量16,并且用數(shù)組數(shù)組實(shí)現(xiàn)谓娃。在ThreadLoca中通過(guò)set方法類(lèi)添加數(shù)據(jù)脚乡,從源碼中可以看出實(shí)際是調(diào)用了ThreadLocalMap類(lèi)的set方法,我們就先來(lái)看看set方法是怎么實(shí)現(xiàn)的吧。
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
int i = key.threadLocalHashCode & (len-1);
通過(guò)ThreadLocal的threadLocalHashCode 參數(shù)奶稠,可以理解為HashCode(int類(lèi)型hash值俯艰,每個(gè)ThreadLocal對(duì)應(yīng)一個(gè)),和table數(shù)組的長(zhǎng)度-1的差做“與”運(yùn)算得到元素在數(shù)組中的下標(biāo)锌订。然后從table數(shù)組中取出對(duì)應(yīng)下標(biāo)的Entry判斷是否為null竹握,如果沒(méi)有發(fā)生沖突(取出的Entry == null)則給對(duì)應(yīng)的下標(biāo)賦值。如果發(fā)生了沖突(取出的Entry != null)辆飘,則比較沖突的Entry的key是否和當(dāng)前的set(ThreadLoacal key,Object value)的參數(shù)key相同啦辐,如果是相同的則覆蓋原來(lái)的value并結(jié)束。如果參數(shù)key和獲取的Entry的key不相等蜈项,這個(gè)時(shí)候就需要解決沖突芹关,這里是通過(guò)向后移動(dòng)下標(biāo),即下標(biāo) +1(這里和HashMap解決沖突不同)來(lái)解決的紧卒。
解決沖突如下:
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
那么ThreadLocalMap添加數(shù)據(jù)的過(guò)程完成了侥衬。ThreadLocal中通過(guò)get方法取出保存的數(shù)據(jù),從源碼中也可以看出實(shí)際是調(diào)用了ThreadLocalMap的getEntry方法跑芳。
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
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);
}
getEntry先是通過(guò)int i = key.threadLocalHashCode & (table.length - 1);取得對(duì)應(yīng)的下標(biāo)轴总,和前面的set方法的獲取下標(biāo)方式相對(duì)應(yīng)。如果取到的Entry值不為null而且key也相同就返回取到的Entry聋亡。由于在添加Entry的時(shí)候有可能發(fā)生沖突肘习,那么在取得時(shí)候就可能不能一次性通過(guò)下標(biāo)取到對(duì)應(yīng)的值,如果發(fā)生這樣的情況就調(diào)用
getEntryAfterMiss()來(lái)獲取坡倔。
/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
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;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
這里必須和前面解決沖突的思路一致漂佩,如果沒(méi)有一次性取到對(duì)應(yīng)的Entry,下標(biāo)就向后移動(dòng)(+1)罪塔,然后在取出新的下標(biāo)的值進(jìn)行比較投蝉,如果符合條件就返回。如果取出的Entry為null征堪,則返回null瘩缆。
默認(rèn)的ThreadLocalMap容量只有16,如果存放的數(shù)據(jù)多了佃蚜,那么就跟HashMap一樣需要擴(kuò)容庸娱,默認(rèn)情況下當(dāng)存儲(chǔ)的數(shù)據(jù)量超過(guò)容量的2/3的時(shí)候就會(huì)擴(kuò)容為之前容量的2倍。
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Double the capacity of the table.
*/
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;
}
相信你看到這里對(duì)ThreadLocal有更深入的理解了吧谐算。