ThreadLocal如何使得一个线程只有一个Looper?
一、数据结构
一个线程当中,存有一个ThreadLoacalMap变量:
Thread.java中,有:
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap,简单来说就是一个类似Map的数据结构,它存储着键值对
ThreadLocalMap中有个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;
}
}
也就是说,在这个哈希map中的键值对Entry继承自ThreadLocal的弱引用,用传入的ThreadLocal的弱引用作为key,用任意对象Object作为值。
即:

这个数据结构的主要存储功能实现如下:
static class ThreadLocalMap{
private Entry[] 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);
}
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();
}
}
用threadlocal键的哈希code按位与上哈希表的长度得到在哈希表中的位置。从而给Entry数组table第i个位置赋值,赋为当前threadlocal为键和要存储的对象为值的Entry。
那么ThreadLocal往ThreadLocal中存储什么类型的值呢?
public class ThreadLocal<T> {
}
泛型T则为往ThreadLocalMap放的值的类型。
二、流程分析
如题目,ThreadLocal如何使得一个线程只有一个Looper?
首先看Looper.java,与ThreadLocal相关的代码:
public final class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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> 对象的get方法判断是否哈希map中已经存过Looper,有即抛异常,没有就存入一个new Looper(quitAllowed)对象。
我们来看ThreadLocal中的get方法:
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();
}
首先获取当前线程的ThreadLocalMap,如果map为空,返回的就是setInitialValue();
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;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
就是说,如果当前线程里的ThreadLocalMap为空,就创建一个ThreadLocalMap,这个map里先存上第一个键值对--即当前threadLocal为键,null为值。
setInitialValue()返回的是null。
回到ThreadLocal的get方法:
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
如果map部位空,就根据键找j键值对,找到了,就返回键值对中的值。找不到,还是会调用setInitialValue(),存上键值对--即当前threadLocal为键,null为值。
所以呢,由于一个线程跟一个ThreadLocalMap是绑定的,如果这个Map中存有当前Looper里的sThreadLocal为键的键值对,就不会再存粗Looper而会抛异常了,反之,就会存入当前Looper里的sThreadLocal为键,new Looper(quitAllowed)为值的对。