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,从而实现了线程隔离。