ThreadLocal是一个线程内部的数据存储类,在主线程中实例化的ThreadLocal对象会在每个子线程中生成一个副本,这个副本是线程隔离的,只能在当前线程下才能访问。Android中的Looper、ActivityThread以及AMS中都用到了ThreadLocal。
这里分析的源码版本是JDK 1.8。
先从一个使用范例开始,在主线程中实例化一个ThreadLocal对象,然后在Thread#main和Thread#1中分别设置它的值并输出。
// 使用范例
ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<>();
// Thread Main
booleanThreadLocal.set(true);
System.out.println("[ThreadMain#main]booleanThreadLocal = " + booleanThreadLocal.get());
// Thread #1
new Thread("Thread#1") {
@Override
public void run() {
booleanThreadLocal.set(false);
System.out.println("[ThreadMain#1]booleanThreadLocal = " + booleanThreadLocal.get());
}
}.start();
// Thread #2
new Thread("Thread#2") {
@Override
public void run() {
System.out.println("[ThreadMain#2]booleanThreadLocal = " + booleanThreadLocal.get());
}
}.start();
输出结果如下
[ThreadMain#main]booleanThreadLocal = true
[ThreadMain#1]booleanThreadLocal = false
[ThreadMain#2]booleanThreadLocal = null
这说明同一个ThreadLocal对象在不同线程中的副本是线程独立的,且ThreadLocal对象在子线程自动生成的副本不会拷贝值而是自动初始化。
下面看 ThreadLocal源码,主要是set和get方法
public class ThreadLocal<T> {
/**
设置ThreadLocal在当前线程的副本为指定值value。
大多数子类都不需要重写此方法, 完全依赖于 {@link #initialValue} 方法来设置ThreadLocal的值。
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //返回一个Thread的实例变量,它存储ThreadLocal的值
if (map != null)
map.set(this, value);// Thread提供一个保存ThreadLocal的table,通过遍历比较ThreadLocal.key来查找需要的ThreadLocal,并为其赋值。如果没有找到,就向table里插入一个新值。
else
createMap(t, value);
}
//
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//
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);//阈值
}
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
// 这是用来存储ThreadLocal对象的数组
private Entry[] table;
//
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);//这是一个巧妙的hash算法
// 这个循环说明table里使用开放定址法来解决hash冲突,hash值取决于啥
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
// 通过直接比较引用地址来判断是否要修改的ThreadLocal
if (k == key) {
e.value = value;
return;
}
// table里的ThreadLocal为null?这是为何?
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 到这里说明这是一个新插入的ThreadLocal
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
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在当前线程的副本的值,如果还没有设置值,则返回{@link #initialValue}方法返回的值。
可以重写{@link #initialValue}来修改初始化的值。
*/
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的set和get方法可以看出,它们操作的ThreadLocal对象都来自当前线程的ThreadLocal.ThreadLocalMap对象的table数组,因此在不同线程中访问同一个ThreadLocal的set和get方法,它们对ThreadLocal所做的读/写操作仅限于各自线程内部,所以ThreadLocal可以在多个线程中互不干扰地存储和修改数据。
阅读材料:
《Android开发艺术探索》