ThreadLocal

ThreadLocal 是一个本地线程副本变量工具类。内部是一个弱引用的 Map 来维护。允许你为每个线程创建独立的变量副本,从而避免线程之间的并发问题。在多线程环境中,通常需要确保多个线程不会共享相同的变量,ThreadLocal 正是解决这个问题的一种机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ThreadLocalExample {
// 创建一个 ThreadLocal 变量
private static final ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);

public static void main(String[] args) {
// 启动两个线程来演示 ThreadLocal 的作用
Thread thread1 = new Thread(new Task(), "Thread-1");
Thread thread2 = new Thread(new Task(), "Thread-2");

thread1.start();
thread2.start();
}

static class Task implements Runnable {
@Override
public void run() {
// 获取当前线程的 ThreadLocal 变量副本的值
Integer value = threadLocalValue.get();
System.out.println(Thread.currentThread().getName() + " 初始值: " + value);

// 修改 ThreadLocal 变量的值
threadLocalValue.set(value + 1);
System.out.println(Thread.currentThread().getName() + " 修改后的值: " + threadLocalValue.get());
}
}
}

核心概念

线程局部变量:每个线程都会拥有自己独立的 ThreadLocal 变量副本。一个线程的副本只属于该线程,其他线程无法访问或修改这个副本。

避免共享状态:使用 ThreadLocal 可以避免多线程环境下的共享状态问题,因为每个线程操作的都是自己的变量副本。

线程安全性ThreadLocal 提供了一种简单的线程安全方式,避免了显式的同步(synchronized)。

使用场景

数据库连接管理:可以为每个线程分配一个独立的数据库连接对象,以避免多个线程竞争同一个连接资源。

Session 管理:在 Web 应用中,可以使用 ThreadLocal 存储每个用户的 Session 信息。

避免参数传递:在多个方法调用链中,可以使用 ThreadLocal 传递数据,而不需要在方法参数中显式传递。

源码

数据结构

Thread 类有一个类型为 ThreadLocal.ThreadLocalMap 的实例变量 threadLocals,也就是说每个线程有一个自己的 ThreadLocalMap

ThreadLocalMap 有自己的独立实现,可以简单地将它的 key 视作 ThreadLocalvalue 为代码中放入的值(实际上 key 并不是 ThreadLocal 本身,而是它的一个弱引用)。

每个线程在往 ThreadLocal 里放值的时候,都会往自己的 ThreadLocalMap 里存,读也是以 ThreadLocal 作为引用,在自己的 map 里找对应的 key,从而实现了线程隔离。

内存泄露风险

ThreadLocalMap 中的键是弱引用,值是强引用。即使 ThreadLocal 被垃圾回收了,值仍然可能被持有,导致内存泄漏。所以在不再需要时,应调用 ThreadLocal.remove() 方法清理相关数据。

强引用:我们常常 new 出来的对象就是强引用类型,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足的时候
弱引用:使用 WeakReference 修饰的对象被称为弱引用,只要发生垃圾回收,若这个对象只被弱引用指向,那么就会被回收

set() 方法

set() 方法具体流程如下:

  1. 获取 Thread 中的 ThreadLocalMap
  2. 如果不存在,则创建新的 ThreadLocalMap
  3. 如果存在,则设置 map 中的值,key 为当前 ThreadLocal, value 为要设置的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}