「源码」ThreadLocal 存储线程本地变量

ThreadLocal 顾名思义就是 线程本地数据 的意思,用于在不同线程之间独立的存取数据,这个数据在每个线程都有一个副本,不同线程存取过程不会相互影响。表现出来的效果就是,我在 A 线程存了一个 a,则我只能在 A 线程再取到、更改这个 a,我在 B 线程是拿不到这个值的。

数据的存储结构

线程独立的数据是如何被存储的呢?

数据其实仍然是被存储在各自线程中,由各自线程去维护,这样实现线程间数据独立的同时,也降低了维护数据的成本,大家管好自己的数据就可以了。

ThreadLocalMap 的数据结构大致如下,看的出来他本质是一个 Entry 数组,EntryWeakRef 的子类,因此他内部存储了 ThreadLocalObject 两个对象。

1
2
3
4
5
6
class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
}
private Entry[] table;
}

在每个线程中都单独维护一个 ThreadLocalMap,初始值为 null,他本身是一个 Map 结构,里面是一个数组,当使用 ThreadLocal 存取数据时通过对 ThreadLocal 进行 hash 处理获得一个数组下标,ThreadLocal 和需要存储的数据对象被打包成一个 Entry 放入数组的指定位置。

1
2
3
4
5
6
7
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 如何保证线程间数据是互相独立的?

我们通常会获得一个 ThreadLocal 对象,并使用它在不同线程做存取数据的操作。

1
2
3
4
5
6
7
8
9
// 1.8 之后可以使用一个工厂函数返回初始值
ThreadLocal<Integer> integerThreadLocal = ThreadLocal.withInitial(() -> 100);
// 1.8 之前在子类重写放啊返回初始值
ThreadLocal stringThreadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "hahha";
}
};

当我们获取数据时,首先借助 Thread.currentThread() 拿到当前方法执行的线程,再从线程中获取本线程维护 ThreadLocalMap 对象,他是一个 Entry(ThreadLocal.hash, Object) 数组,那么此时我么可以将当前的 ThreadLocalhash 处理后,定位到相应的数组下标,取出对应的 Entry 从而拿到里面的 value,如果取不到就返回初始值,简单看一下 get() 的代码会更清晰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public T get() {
// 取到线程
Thread t = Thread.currentThread();
// 拿到线程里面的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 用 ThreadLocal 做 hash 定位对应的 Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
// 取到 Entry 里面的 value
T result = (T)e.value;
return result;
}
}
// 返回初始值
return setInitialValue();
}

当往 ThreadLocal 中存入数据时,逻辑也是一样的,唯一的不同时,如果线程中的 ThreadLocalMap 没有创建时,此时会创建一个新的 ThreadLocalMap 并将数据存储进去

1
2
3
4
5
6
7
8
9
10
11
12
public void set(T value) {
// 获取到线程
Thread t = Thread.currentThread();
// 获取线程维护的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 对 ThreadLocal 做 hash 拿到数组下标,打包 TreadLocal 和 value 存储
if (map != null)
map.set(this, value);
else
// 创建新的 ThreadLocalMap 并存储
createMap(t, value);
}
------ 本文结束 🎉🎉 谢谢观看  ------