相信了解过handler机制的同学对ThreadLocal都不会陌生,它的作用是为不同线程提供独立的Looper副本。除此之外,以线程为作用域的并且不同线程需要独立的变量的数据都可以采用ThreadLocal。这样可能不太好理解,我们举个例子,假设我们在线程中需要随时随地获取一个对象,我们首先想到的是将这个对象设置成静态的全局变量,这种方法是可以接受的,但是如果每个线程都需要这个对象我们是不是要为每个线程都提供这样一个对象,这样看来就有些不妥,而ThreadLocal恰恰完美的解决了这一点。
用例
private ThreadLocal<Boolean> threadLocal = new ThreadLocal();
onCreate:
threadLocal.set(false);
Log.d(TAG, "main: "+threadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set(true);
Log.d(TAG, "t1: "+threadLocal.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "t2: "+threadLocal.get());
}
}).start();
结果:
2019-06-03 11:04:28.111 14149-14149/com.example.androidplan D/KloseMainActivity: main: false
2019-06-03 11:04:28.112 14149-14172/com.example.androidplan D/KloseMainActivity: t1: true
2019-06-03 11:04:28.116 14149-14173/com.example.androidplan D/KloseMainActivity: t2: null
从日志上可以看到,和预想的结果一样,三个线程的值互不影响,t2线程没有设置所以为null。还有一点,用户可以自定义initialValue()初始化方法,来设置threadLocal初始值。
源码分析
我们先来看一下源码注释
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its{@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID).
该类提供线程本地变量。这些变量与普通的对应变量的不同之处在于,每个访问一个变量的线程(通过它的{@code get}或{@code set}方法)都有自己独立初始化的变量副本。{@code ThreadLocal}实例通常是希望将状态与线程(例如,用户ID或事务ID)关联的类中的私有静态字段。
get()
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程内部维护的一个ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//如果map为空,说明还没初始化,走setInitiaValue()
if (map != null) {
//根据当前threadlocal对象查询对应的entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//得到value并返回
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
//每个线程内部都维护了一个ThreadLocalMap
return t.threadLocals;
}
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 T setInitialValue() {
//这里提供给用户自定义初始值
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
//插入初始化的value值
map.set(this, value);
else
//new一个新的map
createMap(t, value);
//将自定义的初始化值返回
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化一个容量为16的Entry数组
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
//设置阈值
setThreshold(INITIAL_CAPACITY);
}
//阈值为容量的2/3
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
get总结:
首先获取当前线程的ThreadLocalMap,然后根据当前threalocal实例查询对应的Entry,不为空,则返回value,ThreadLocalMap或者Entry为空走初始化方法。
set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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();
//找到对应key并替换value
if (k == key) {
e.value = value;
return;
}
//key==null 添加新的key,value,并清理陈旧数据
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
//如果没有清除陈旧项且超过了阈值,就重新hash
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private void rehash() {
//删除表中所有陈旧项目
expungeStaleEntries();
//删除之后还超过阈值的3/4,就进行数组扩容,避免迟滞
if (size >= threshold - threshold / 4)
resize();
}
set总结:
遍历table,如果key相等,替换value,如果key为null,用新key、value替换,同时清理历史key=null的陈旧数据(个人猜想应该是gc之后的),如果超过阀值,就需要再哈希。清理一遍陈旧数据之后 ,如果size还大于等于3/4阀值就把table扩容到原来的两倍,之所以设置为3/4,是为了避免迟滞,最后把原有数据重新哈希散列进新table。
另一个remove方法就不再赘述了。