前言
上篇文章 深入理解 Handler 消息机制 中提到了获取线程的 Looper 是通过 ThreadLocal
来实现的:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
每个线程都有自己的 Looper,它们之间不应该有任何交集,互不干扰,我们把这种变量称为 线程局部变量 。而 ThreadLocal
的作用正是存储线程局部变量,每个线程中存储的都是独立存在的数据副本。如果你还是不太理解,看一下下面这个简单的例子:
public static void main(String[] args) throws InterruptedException {
ThreadLocal<Boolean> threadLocal = new ThreadLocal<Boolean>();
threadLocal.set(true);
Thread t1 = new Thread(() -> {
threadLocal.set(false);
System.out.println(threadLocal.get());
});
Thread t2 = new Thread(() -> {
System.out.println(threadLocal.get());
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(threadLocal.get());
}
执行结果是:
false
null
true
可以看到,我们在不同的线程中调用同一个 ThreadLocal 的 get() 方法,获得的值是不同的,看起来就像 ThreadLocal 为每个线程分别存储了不同的值。那么这到底是如何实现的呢?一起来看看源码吧。
以下源码基于 JDK 1.8 , 相关文件:
ThreadLocal
首先 ThreadLocal 是一个泛型类,public class ThreadLocal<T>
,支持存储各种数据类型。它对外暴露的方法很少,基本就 get()
、set()
、remove()
这三个。下面依次来看一下。
set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 获取当前线程的 ThreadLocalMap
if (map != null)
map.set(this, value);
else
createMap(t, value); // 创建 ThreadLocalMap
}
这里出现了一个新东西 ThreadLocalMap
,暂且就把他当做一个普通的 Map。从 map.set(this, value)
可以看出来这个 map 的键是 ThreadLocal
对象,值是要存储的 value
对象。其实看到这,ThreadLocal 的原理你应该基本都明白了。
每一个
Thread
都有一个ThreadLocalMap
,这个 Map 以ThreadLocal
对象为键,以要保存的线程局部变量为值。这样就做到了为每个线程保存不同的副本。
首先通过 getMap()
函数获取当前线程的 ThreadLocalMap :
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
原来 Thread 还有这么一个变量 threadLocals
:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class.
*
* 存储线程私有变量,由 ThreadLocal 进行管理
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
默认为 null
,所以第一次调用时返回 null ,调用 createMap(t, value)
进行初始化:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get()
set()
方法是向 ThreadLocalMap
中插值,那么 get()
就是在 ThreadLocalMap
中取值了。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 获取当前线程的 ThreadLocalMap
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 中寻找当前 ThreadLocal 对应的 value 值。如果 Map 为空,或者没有找到 value,则通过 setInitialValue()
函数设置初始值。
private T setInitialValue() {
T value = initialValue(); // 为 null
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
setInitialValue()
和 set()
逻辑基本一致,只不过 value 是 null
而已。这也解释了文章开头的例子会输出 null。当然,在 ThreadLocal 的子类中,我们可以通过重写 setInitialValue()
来提供其他默认值。
remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
remove()
就更简单了,根据键直接移除对应条目。
看到这里,ThreadLocal
的原理好像就说完了,其实不然。ThreadLocalMap
是什么样的一个哈希表呢?它是如何解决哈希冲突的?它是如何添加,获取和删除元素的?可能会导致内存泄露吗?
其实 ThreadLocalMap
才是 ThreadLocal
的核心。ThreadLocal 仅仅只是提供给开发者的一个工具而已,就像 Handler 一样。带着上面的问题,来阅读 ThreadLocalMap 的源码,体会 JDK 工程师的鬼斧神工。
ThreadLocalMap
Entry
ThreadLocalMap 是 ThreadLocal 的静态内部