关于 ThreadLocal
ThreadLocal
,顾名思义,此类是用来提供线程本地变量。ThreadLocal 类设计成了带泛型,一个 ThreadLocal 对象可以在不同的线程中,分别存入一个特定类型的变量。每个线程仅可以访问各自存入的变量,从而达到线程隔离。
使用与举例
ThreadLocal 类使用的接口设计地非常简单,仅需要通过 set(T value)
方法设置线程本地变量,通过 get()
获取该变量即可:
final ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("value1");
System.out.println(threadLocal.get());
// 输出结果为 “value1”
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(threadLocal.get());
// 输出结果为 “null”
}
}).start();
举个具体的例子将很好的说明 ThreadLocal 的使用场景:在 Android 的线程消息机制中,每个线程需要拥有“自己”的 Looper 对象来分开处理线程内的队列消息,这其中便是通过 ThreadLocal 类来实现的:
public final class 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));
}
// 通过 Looper.myLooper() 便可以返回当前线程特定的 Looper 对象
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
}
基本原理
为了达到每次对 ThreadLocal 对象进行 set(T value)
操作时,存入的对象“仅属于”当前的线程,引入了一个可以存储多个 key-value 的 ThreadLocaMap 类。
每个线程的 Thread 类对象中持有一个该 ThreadLocalMap 对象:
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
先来看下 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);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
在上面的代码中,若当前线程 Thread 对象的 threadLocals
为空,则先创建一个新的 ThreadLocalMap。否则,把目标 value 存储进此ThreadLocalMap 中,整体结构如下:
同样的,get()
方法实现也是通过从线程专属的 ThreadLocalMap 对象中根据目标 key(ThreadLocal 对象自身)来获取到目标 value,若目标 key 不存在,则默认返回 null:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue(); // 默认值为 null
}
换言之,ThreadLocal 对象本身不会直接存储 key-value,而是对外提供一套操作的 get()/set()
接口,以自身为 key,存放在每个线程 Thread 对象特有的一个 ThreadLocalMap 数据结构里,从而实现线程隔离的对象存储。
ThreadLocalMap
ThreadLocal 的运作机制核心实现在 ThreadLocalMap 中,下面来一探其究竟。
基本结构
ThreadLocalMap 为 ThreadLocal 的静态内部类,其效果类似于一个 Map,可以存放多个 key-value。ThreadLocalMap 内部也有一个“特殊”的 Entry 类来表示每个 key-value:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** ThreadLocal 关联的变量 */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
之所以说是特殊的,是因为该 Entry 继承自弱应用类 WeakReference,它的 key 为 ThreadLocal 对象并将其设置为自身的弱引用对象,value 则为 ThreadLocal 目标存入的变量。
由于一个 ThreadLocal 对象可能会在不同的线程中调用设置,每个线程又会各自持有一个 ThreadLocalMap 对象,因此通过对 ThreadLocal 设置为弱引用的方式,在 ThreadLocal 对象不再被外部持用使用时自动释放。
接着看 ThreadLocalMap 内部的 Entry 数组结构,这里和 HashMap 等 Map 实现类结构类似:
static class ThreadLocalMap {
/** 数组初始大小 */
private static final int INITIAL_CAPACITY = 16;
/** Entry 数组 */
private Entry[] table;
/** 当前存入 table 的 Entry 个数 */
private int size = 0;
/** 数组需要拓展大小的临界值 */
private int threshold;
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
...
/** 构造方法 */
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY]; // 初始化 Entry 数组
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
set() 方法实现
接着来看 ThreadLocalMap 的 set()
方法实现:
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); // 取哈希值合适的低位为节点 index
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) { // 新旧 key 相同,更新 value 变量
e.value = value;
return;
}
if (k == null) { // key 为空(弱引用已释放),替换该节点为新的 Entry
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value); // 在目标节点插入新的 Entry
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
在 set()
方法的实现中,目标是把传入的 key-value 组成一个 Entry 存在进数组中。为此和 HashMap 的实现类似,需要先通过一定的哈希码离散算出存放的节点 index,这里哈希码 threadLocalHashCode
的算法实现比较简单,示例如下:
public class ThreadLocal<T> {
// 此 ThreadLocal 实例的 hashcode
private final int threadLocalHashCode = nextHashCode();
/**
* 全局静态变量,自动更新
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
/**
* 返回下一个哈希码
*/
private static int nextHashCode() {
// 这里使用了「斐波那契散列法」,来保证离散度
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}
在算出要存在的目标节点后,判断当前目标节点是否有已存在的 Entry,整个描述如下:
getEntry() 方法实现
基于上面 set()
方法的实现原理,可知目标 Entry 优先存放在 key(ThreadLocal) 的哈希码低位算出来的数组节点,否则再考虑存放在其他可用的节点。那么 getEntry()
方法的实现则比较简单了:
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
// 目标节点存放的 Entry 存在且 key 相同,直接返回该 Entry
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key) // 在其他节点位置找到目标 key
return e;
if (k == null) // 当前 key 已释放,擦除该 Entry
expungeStaleEntry(i);
else
i = nextIndex(i, len); // 下一节点,遍历数组
e = tab[i];
}
return null; // 否则目标 key 不存在,返回 null
}
总结
- 使用 ThreadLocal 类可以存储“专属”当前线程的变量,从而实现在不同线程中操作不同变量等特定需求。
- ThreadLocal 类不直接储存变量数据,而仅仅是提供对外的接口,数据真正存放在线程 Thread 对象中的 ThreadLocalMap 数据结构中。
- ThreadLocalMap 内通过以 ThreadLocal 对象为 key,以数组的结构实现了存储多个 key-value 的 Map 能力。