Looper中的ThreadLocal
最早知道ThreadLocal是在Looper的源码里,用一个ThreadLocal保存了当前的looper对象。
//一个静态ThreadLocal对象,因此一个进程里的所有Looper都共用这个ThreadLocal
// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal sThreadLocal = new ThreadLocal();
//所有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> {
/** 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指令有关,感兴趣的小伙伴自行研究吧~