Looper中的ThreadLocal
最早知道ThreadLocal是在Looper的源碼裏,用一個ThreadLocal保存了當前的looper對象。
//一個靜態ThreadLocal對象,因此一個進程裏的所有Looper都共用這個ThreadLocal
// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//所有Looper都需要調用的prepare方法,實例化新的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保存的是線程內獨立的對象,所以在哪個線程調用,取到的就是哪個線程的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();
}
當時就查了下ThreadLocal,發現是保存線程私有對象的容器,以爲就是一個類似hashmap,用線程做key,存value。最近看了下並不是如此,實際上是以ThreadLocal自身爲key來存儲對象的。於是來學習下ThreadLocal源碼是如何做到保存線程私有對象的。
ThreadLocal和ThreadLocalMap以及Thread的關係
ThreadLocal、ThreadLocalMap、Thread之間的關係和我們下意識中想的不太一樣,不過一步步看下去之後就能明白爲啥ThreadLocal能保存線程私有對象了。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
這個是Thread源碼,每一個Thread都有一個ThreadLocalMap對象,而ThreadLocalMap是ThreadLocal的內部類,是實際存儲對象的容器。
static class ThreadLocalMap {
//可以看到這裏持有的ThreadLocal對象是弱引用,
//防止線程不死(比如android主線程)ThreadLocal無法被回收。
//這裏可以看到Entry的key是ThreadLocal,value是要保存的對象。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//線程初始化後,會實例化一個ThreadLocalMap,map裏有table,table裏存Entry。
//因此,一個線程對應一個ThreadLocalMap,一個ThreadLocalMap對應多個Entry。
//每個Entry對應一個ThreadLocal作爲key,所以一個ThreadLocal只能存一個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();
//驗證k相等,replace value的值
if (k == key) {
e.value = value;
return;
}
//有空的位置,存入
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//到這裏還沒return,說明table存滿了,需要擴容
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);
}
}
基本關係知道了,最後再來看看ThreadLocal的方法:
//ThreadLocal的get方法
public T get() {
//獲取當前線程
Thread t = Thread.currentThread();
//拿到當前線程的ThreadLocalMap對象
ThreadLocalMap map = getMap(t);
if (map != null) {
//因爲ThreadLocalMap存的時候就是拿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();
}
//獲取當前線程的ThreadLocalMap對象
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//ThreadLocal的set方法
public void set(T value) {
//獲取當前線程
Thread t = Thread.currentThread();
//拿到當前線程的ThreadLocalMap對象
ThreadLocalMap map = getMap(t);
//this作key傳入存儲value
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
那麼現在基本完全清楚ThreadLocal與Thread還有ThreadLocalMap之間的關係了。
每個Thread都有一個成員變量ThreadLocalMap。這個ThreadLocalMap對象不是public,所以外部調用不了,可以看做是線程私有對象。
ThreadLocalMap存了一個table,table裏保存了一些entry,每個entry對應一個key和value,而這個key就是ThreadLocal對象。
因此一個ThreadLocal只能存一個value,但是可以通過new多個ThreadLocal來保存多個線程私有對象。
ThreadLocal內存泄露
在上面的源碼中我們看到Entry裏持有的ThreadLocal對象是弱引用持有,因此ThreadLocal不會因爲線程持有而泄露,比如我們Android的主線程,正常使用過程中是不會掛掉的。
但是Enrty的value的是強引用的,因此ThreadLocal中的value還是會因爲線程持有而無法回收。如果繼續看源碼的話,會發現在ThreadLocalMap的resize和expungeStaleEntry方法裏會檢查key爲空的value值爲空幫助GC。
因此爲了避免value內存泄露,我們需要在ThreadLocal不需要的時候主動remove掉。
併發修改問題
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。因此這個threadLocalHashCode就十分重要了。
這裏需要保證threadLocalHashCode是唯一的,否則兩個線程同時創建ThreadLocal得到相同的hashcode,那就破壞了ThreadLocalMap的線程私有特性了。
這裏生成threadLocalHashCode是通過一個靜態對象nextHashCode不斷增加來獲得的。那怎麼保證多個進程併發實例化ThreadLocal對象,不會生成相同的hashcode呢?
答案是AtomicInteger,通過這個類來保證變量自增操作在多線程操作時候的原子性。
我們知道Synchronized也可以保證原子性,但相比於它,AtomicInteger類是通過非阻塞方法來實現原子性的,需要的性能開銷更小。
這種非阻塞實現原子性的方法和處理器的CAS指令有關,感興趣的小夥伴自行研究吧~