Handler(三)--ThreadLocal
系列目录: Handler机制原理
ThreadLocal介绍
ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
虽然在不同线程中访问的是同一个 ThreadLocal 对象,但是它们通过 ThreadLocal 获取到的值却是不一样的。
一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal。
代码解析
通过前面的整体执行流程代码跟踪中我们可以看到在实例化Looper的时候我们使用了ThreadLocal对象来存储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对象,下一步继续看ThreadLocal .set()方法
//ThreadLocal#set()
public void set(T value) {
//1. 取当前线程对象
Thread t = Thread.currentThread();
//2. 取当前线程的数据存储容器
ThreadLocalMap map = getMap(t);
if (map != null)
//3. 以当前ThreadLocal实例对象为key,存值
map.set(this, value);
else
//4. 新建个当前线程的数据存储容器,并以当前ThreadLocal实例对象为key,存值
createMap(t, value);
}
首先,获取当前线程对象。
接着,调用了 getMap() 方法,并传入了当前线程,看看这个 getMap() 方法:
//ThreadLocal#getMap()
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看出threadLocals为Thread的成员变量,可以去查看一下给其赋值的地方
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Thread 中的 threadLocal 成员变量初始值为 null,并且在 Thread 类中没有任何赋值的地方,只有在 ThreadLocal 中的 createMap() 方法中对其赋值,而调用 createMap() 的地方就两个:
set()
和setInitialValue()
,而调用 setInitialValue() 方法的地方只有get()
。
也就是说,ThreadLocal 的核心其实也就是在get()
和set()
,搞懂这两个方法的流程原理,那么也就基本理解 ThreadLocal 这个东西的原理了。
上面实例化了ThreadLocalMap,在这里先整体了解一下ThreadLocalMap类
static class ThreadLocalMap {
//存储的数据类型
static class Entry extends WeakReference<ThreadLocal<?>> {
...
}
//存储的数组
private Entry[] table;
//实例化ThreadLocalMap和Entry,并将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);
}
//根据ThreadLocal获取数据
private Entry getEntry(ThreadLocal<?> key) {
...
}
//根据ThreadLocal存储数据
private void set(ThreadLocal<?> key, Object value) {
...
}
}
这里面的set存储和getEntry获取是怎么执行的不在做过多的解析,总之这里面有一个数组,数组存储数据。
大体介绍如下:
首先,容器是各自线程对象的成员变量,也就是数据其实就是交由各自线程维护,那么不同线程即使调用了同一 ThreadLocal 对象的同一方法,取的数据也是各自线程的数据副本,这样自然就可以达到维护不同线程各自相互独立的数据副本,且以线程为作用域的效果了。
同时,在将数据存储到各自容器中是以当前 ThreadLocal 对象实例为 key 存储,这样,即使在同一线程中调用了不同的 ThreadLocal 对象的 get() 方法,所获取到的数据也是不同的,达到同一线程中不同 ThreadLocal 虽然共用一个容器,但却可以相互独立运作的效果。
下一步继续看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();
}
ThreadLocal.getMap()
和ThreadLocalMap.getEntry()
方法前文中已经有描述,此处不再说明。唯一一个我们还没解析的方法就是ThreadLocalsetInitialValue()
。
//ThreadLocal#setInitialValue()
private T setInitialValue() {
//1. 获取初始值,默认返回Null,允许重写
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//2. 创建线程t的数据存储容器:threadLocals
createMap(t, value);
//3. 返回初始值
return value;
}
首先会通过 initialValue() 去获取初始值,默认实现是返回 null,但该方法允许重写。然后同样去获取当前线程的数据存储容器 map,为null,所以这里会走 createMap(),而 createMap() 之前分析过了,就是去创建参数传进去的线程自己的数据存储容器 threadLocals,并将初始值保存在容器中,最后返回这个初始值。