Looper中的ThreadLocal
最早知道ThreadLocal是在Looper的源碼里,用一個(gè)ThreadLocal保存了當(dāng)前的looper對(duì)象。
//一個(gè)靜態(tài)ThreadLocal對(duì)象,因此一個(gè)進(jìn)程里的所有Looper都共用這個(gè)ThreadLocal
// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//所有Looper都需要調(diào)用的prepare方法,實(shí)例化新的Looper并保存到ThreadLocal
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
//由于ThreadLocal保存的是線程內(nèi)獨(dú)立的對(duì)象,所以在哪個(gè)線程調(diào)用,取到的就是哪個(gè)線程的Looper
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
當(dāng)時(shí)就查了下ThreadLocal,發(fā)現(xiàn)是保存線程私有對(duì)象的容器,以為就是一個(gè)類似hashmap,用線程做key,存value。最近看了下并不是如此,實(shí)際上是以ThreadLocal自身為key來存儲(chǔ)對(duì)象的。于是來學(xué)習(xí)下ThreadLocal源碼是如何做到保存線程私有對(duì)象的。
ThreadLocal和ThreadLocalMap以及Thread的關(guān)系
ThreadLocal、ThreadLocalMap、Thread之間的關(guān)系和我們下意識(shí)中想的不太一樣,不過一步步看下去之后就能明白為啥ThreadLocal能保存線程私有對(duì)象了。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
這個(gè)是Thread源碼,每一個(gè)Thread都有一個(gè)ThreadLocalMap對(duì)象,而ThreadLocalMap是ThreadLocal的內(nèi)部類,是實(shí)際存儲(chǔ)對(duì)象的容器。
static class ThreadLocalMap {
//可以看到這里持有的ThreadLocal對(duì)象是弱引用,
//防止線程不死(比如android主線程)ThreadLocal無法被回收。
//這里可以看到Entry的key是ThreadLocal,value是要保存的對(duì)象。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//線程初始化后,會(huì)實(shí)例化一個(gè)ThreadLocalMap,map里有table,table里存Entry。
//因此,一個(gè)線程對(duì)應(yīng)一個(gè)ThreadLocalMap,一個(gè)ThreadLocalMap對(duì)應(yīng)多個(gè)Entry。
//每個(gè)Entry對(duì)應(yīng)一個(gè)ThreadLocal作為key,所以一個(gè)ThreadLocal只能存一個(gè)value。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
//ThreadLocalMap保存方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//通過ThreadLocal的hashcode獲得索引i
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//驗(yàn)證k相等,replace value的值
if (k == key) {
e.value = value;
return;
}
//有空的位置,存入
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//到這里還沒return,說明table存滿了,需要擴(kuò)容
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
//ThreadLocalMap獲取方法,還是通過hashcode獲取table中的索引
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);
}
}
基本關(guān)系知道了,最后再來看看ThreadLocal的方法:
//ThreadLocal的get方法
public T get() {
//獲取當(dāng)前線程
Thread t = Thread.currentThread();
//拿到當(dāng)前線程的ThreadLocalMap對(duì)象
ThreadLocalMap map = getMap(t);
if (map != null) {
//因?yàn)門hreadLocalMap存的時(shí)候就是拿ThreadLocal作key
//因此這里傳入this獲取entry,再拿到value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
//獲取當(dāng)前線程的ThreadLocalMap對(duì)象
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//ThreadLocal的set方法
public void set(T value) {
//獲取當(dāng)前線程
Thread t = Thread.currentThread();
//拿到當(dāng)前線程的ThreadLocalMap對(duì)象
ThreadLocalMap map = getMap(t);
//this作key傳入存儲(chǔ)value
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
那么現(xiàn)在基本完全清楚ThreadLocal與Thread還有ThreadLocalMap之間的關(guān)系了。
每個(gè)Thread都有一個(gè)成員變量ThreadLocalMap。這個(gè)ThreadLocalMap對(duì)象不是public,所以外部調(diào)用不了,可以看做是線程私有對(duì)象。
ThreadLocalMap存了一個(gè)table,table里保存了一些entry,每個(gè)entry對(duì)應(yīng)一個(gè)key和value,而這個(gè)key就是ThreadLocal對(duì)象。
因此一個(gè)ThreadLocal只能存一個(gè)value,但是可以通過new多個(gè)ThreadLocal來保存多個(gè)線程私有對(duì)象。
ThreadLocal內(nèi)存泄露
在上面的源碼中我們看到Entry里持有的ThreadLocal對(duì)象是弱引用持有,因此ThreadLocal不會(huì)因?yàn)榫€程持有而泄露,比如我們Android的主線程,正常使用過程中是不會(huì)掛掉的。
但是Enrty的value的是強(qiáng)引用的,因此ThreadLocal中的value還是會(huì)因?yàn)榫€程持有而無法回收。如果繼續(xù)看源碼的話,會(huì)發(fā)現(xiàn)在ThreadLocalMap的resize和expungeStaleEntry方法里會(huì)檢查key為空的value值為空幫助GC。
因此為了避免value內(nèi)存泄露,我們需要在ThreadLocal不需要的時(shí)候主動(dòng)remove掉。
并發(fā)修改問題
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
ThreadLocal通過自身的threadLocalHashCode來碰撞得到自己在ThreadLocalMap的table里的索引i。因此這個(gè)threadLocalHashCode就十分重要了。
這里需要保證threadLocalHashCode是唯一的,否則兩個(gè)線程同時(shí)創(chuàng)建ThreadLocal得到相同的hashcode,那就破壞了ThreadLocalMap的線程私有特性了。
這里生成threadLocalHashCode是通過一個(gè)靜態(tài)對(duì)象nextHashCode不斷增加來獲得的。那怎么保證多個(gè)進(jìn)程并發(fā)實(shí)例化ThreadLocal對(duì)象,不會(huì)生成相同的hashcode呢?
答案是AtomicInteger,通過這個(gè)類來保證變量自增操作在多線程操作時(shí)候的原子性。
我們知道Synchronized也可以保證原子性,但相比于它,AtomicInteger類是通過非阻塞方法來實(shí)現(xiàn)原子性的,需要的性能開銷更小。
這種非阻塞實(shí)現(xiàn)原子性的方法和處理器的CAS指令有關(guān),感興趣的小伙伴自行研究吧~