ThreadLocal
JDK 提供的 ThreadLocal 可以实现让每个线程都有自己的专属本地变量,虽然每个线程操作的是同一个 ThreadLocal 对象,但是 get()
和 set()
方法操作的变量都保存在每个线程内部,和其他线程互不影响。
举个例子,下面的代码创建了 10 个线程对一个 ThreadLocal 对象进行自增,并输出自增后的值。
// 创建一个 ThreadLocal 对象,内部持有一个 Integer 变量,初始值是 0
static ThreadLocal<Integer> local = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
// 每个线程都对 ThreadLocal 中的 Integer 变量加一再存回
int val = local.get() + 1;
local.set(val);
System.out.println(Thread.currentThread().getName() + ", val = " + local.get());
}, "Thread-" + i).start();
}
Thread.sleep(500); // 等待 500ms
// 主线程获取值,输出默认值 0
System.out.println(Thread.currentThread().getName() + ", val = " + local.get());
}
输出如下,10 个线程的输出都是 1,主线程输出 0,因为主线程没有进行自增。验证了 ThreadLocal 对于每个线程都是独立的副本。
Thread-1, val = 1
Thread-4, val = 1
Thread-2, val = 1
Thread-3, val = 1
Thread-0, val = 1
Thread-7, val = 1
Thread-6, val = 1
Thread-5, val = 1
Thread-8, val = 1
Thread-9, val = 1
main, val = 0
Thread 的原理
知其然还要知其所以然,相信不少人都好奇 ThreadLocal 明明只是一个普通的对象,为什么它能做到“每个线程都有一份独立的副本”?
首先从 Thread
类的源码说起。在 java.lang.Thread
中,有一个名为 threadLocals
的成员变量:
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
这个变量的类型是 ThreadLocalMap
,它是 ThreadLocal
的内部静态类,是一个定制的哈希表结构,专门用于保存当前线程的所有 ThreadLocal 对象及其对应的值。
也就是说,每个线程都有一个独立的 ThreadLocalMap
,这个 map 的键是 ThreadLocal
实例本身,值则是我们调用 set()
时传入的对象。
我们再来看 ThreadLocal
的核心方法 set()
:
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 获取当前现线程中的 ThreadLocalMap
if (map != null) {
// 插入/更新键值对
map.set(this, value); // key 是当前 ThreadLocal 实例,val 是 set() 的值
} else {
createMap(t, value); // map 不存在,创建 map 并插入值
}
}
// 获取线程 t 中的 threadLocals 对象
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 为线程 t 创建 ThreadLocalMap
// key 是当前 ThreadLocal 实例,val 是 set() 的值
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
}
通过这段源码我们可以清楚地看到,每个线程的 ThreadLocalMap
是独立存在的,ThreadLocal
实际上并不直接保存值,而是作为 key 被存入当前线程的 threadLocals
。
所以即使多个线程共享同一个 ThreadLocal
实例,它们操作的也是各自线程中的不同 ThreadLocalMap
,从而实现了线程隔离。