在了解 Handler 的机制时,会接触到 ThreadLocal 、 ThreadLocalMap。那么其在 Handler整个机制里起了什么作用呢?本文带你了解。
Handler 为什么需要 ThreadLocal?
在分析 Looper 的时候,我们常会听到说 Looper 是存放在线程中的。
我们需要一个线程只有一个轮询器Looper 、一个 MessageQueue 。
ThreadLocal 源码分析。
#Looper 类
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
- 为什么要在 Looper 类里面定义一个 ThreadLocal 的实例变量 sThreadLocal?
前面我们提过,我们想要每个线程有且仅有一个 Looper,线程可能已经有了或者还没有创建 Looper,所以需要在 Looper 类暴露方法供给线程查询。
————————————————————————————————————————————
#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));
}
这是主线程中准备 Looper 的源代码。当 new 了一个 Looper 对象,set 到了哪里呢?我们跟着 sThreadLocal.set(new Looper(quitAllowed));
#ThreadLocal
public void set(T value) {
//1.获得正在执行目前代码的线程
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
再来了解一下 ThreadLocalMap,每个线程中都有一个ThreadLocalMap成员变量 threadLocals,这是一个类似于 HashMap的对象,理所当然需要存入键值对。键就是 ThreadLocal 实例(因为可能存在不止一个 ThreadLocal 实例,而当前的这个 Threadlocal 实例是用来存 Looper 的,所以传入 this),值就是 Looper 实例对象。
#ThreadLocal 类中的内部类 ThreadLocalMap
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
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);
}
private void set(ThreadLocal<?> key, Object value) {
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)]) {
if (e.refersTo(key)) {
// 2. 第二种情况
e.value = value;
return;
}
//3. 第三种情况
if (e.refersTo(null)) {
replaceStaleEntry(key, value, i);
return;
}
}
//1.第一种情况
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
}
通过 ThreadLocalMap 中的 set() 方法可以新增或更新数据,这可以分为四种情况:
-
一:通过
hash计算后的位置对应的 Entry 数据为空:直接将数据存入该位置即可。 -
二:位置对应的数据不为空,但 key 值和当前
ThreadLocal hash 计算后的 key值相同:直接将数据更新覆盖到该位置。 -
三:
hash到的位置不为空,key 值和当前 hash 到的 key值不相同,向后遍历且在找到Entry 为 null的位置或者 key 值相同的位置之前,未遇到Entry not null 但 key 为 null的情况:直接存入数据或更新数据。 -
四:
hash到的位置不为空,在向后遍历时遇到了Entry not null但key 为 null(假设该位置下标为x)的情况:-
此时执行
replaceStaleEntry()方法(替换过期数据),从下标x为起点向前遍历,初始化探测式清理的开始位置:slotToExpunge = staleSlot = x,进行探测式数据清理。 -
从
staleSlot开始向前遍历查找其他的过期数据,并更新清理过期数据的起始下标slotToExpunge(遇到 key 为 null 的位置则更新slotToExpu nge = 当前下标),直到遇到Entry = null停止向前遍历。 -
从
staleSlot开始向后遍历,直至遇到Entry = null或者key = hash 后得到的 key。 *Entry = null:将数据覆盖替换掉staleSlot位置上的Entry。
-
key = hash 后得到的 key:将数据更新,然后与staleSlot的 Entry 交换。
-
在前 3 步的过程中若发现有两个或以上的
key = null则调用cleanSomeSlots(expungeStaleEntry(slotToExpunge), len)方法清理过期元素。(从slotToExpunge开始向后检查并清理过期元素,此时主要是通过expungeStaleEntry()和cleanSomeSlots()两个方法工作。)
-
以上四种情况参考:链接juejin.cn/post/711302…
在了解了 set 之后,get 就很简单了。
#ThreadLocal 类
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//得到一个 Entry 对象,内部保存着对应的 Looper 对象。
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
总结
每个线程只有一个 Looper。每个线程有一个 ThreadLocal 的内部类 ThreadLocalMap 成员变量threadLocals,类似于 HashMap 结构。以 Looper 为例,threadLocals 里维护一个Enrty数组,key 为 ThreadLocal 实例的哈希值(因为用一个 ThreadLocal 代表一类值),value 为 Entry 对象,Entry 对象内保存着 Looper 实例对象。