Java并发编程学习笔记:ThreadLocal
一、ThreadLocal
ThreadLocal的主要作用是为每个线程提供一个独立的变量副本,这样在多线程环境下,每个线程可以拥有自己的变量值,而不会与其他线程共享同一份变量值,从而避免了因并发访问带来的数据同步问题。
基本用法:
-
每个 ThreadLocal 实例都维护了一个与当前执行线程关联的变量副本。
-
线程通过调用 ThreadLocal 的 set(T value) 方法来设置其对应的变量值,并且只能被当前线程读取或修改。
-
要获取当前线程所绑定的变量值,使用 get() 方法,返回的是当前线程私有的那个变量副本。
-
如果需要清除当前线程绑定的变量,可以调用 remove() 方法。
应用场景:
- 在多线程环境中,每个线程可能需要维护自己的上下文信息,例如数据库连接、用户会话、日志记录器等。
- 避免频繁传递相同的参数给同一个线程中的多个方法。
- 在无需进行昂贵同步操作的情况下实现线程安全。
二、ThreadLocalMap
ThreadLocal 的底层原理主要围绕其如何为每个线程维护独立的变量副本。
ThreadLocal 并不直接存储变量值,而是通过一个内部类 ThreadLocalMap 间接实现对线程私有变量的管理。ThreadLocalMap 是一个定制化的哈希表,它的键是 ThreadLocal 实例本身(弱引用),值是需要在每个线程中保持独立状态的对象(强引用)。
-
当调用set(T value) 方法时,会根据当前执行线程找到与之关联的 ThreadLocalMap,然后将 value 存储到该 ThreadLocalMap 中,键就是当前 ThreadLocal 实例自身。
-
调用 get() 方法时,同样会从当前执行线程的 ThreadLocalMap 中查找对应的键(即 ThreadLocal 实例),并返回相应的值。如果之前没有设置过值,则返回初始值或 null。
-
每个 Thread 对象都有一个名为 threadLocals 或 inheritableThreadLocals 的成员变量,它们都是 ThreadLocalMap 类型的,用于存储不同 ThreadLocal 对象所绑定的线程局部变量。
三、内存泄漏
ThreadLocalMap 使用的是弱引用作为键,当 ThreadLocal 对象在其他地方没有强引用指向它时,垃圾回收器可能会回收这个 ThreadLocal 对象,而 ThreadLocalMap 中对应的条目因为键已经变为 null,所以不会被自动清理,从而导致 ThreadLocalMap 中存在无用但无法被回收的 Entry,形成内存泄漏。
为了避免这个问题,在 ThreadLocalMap 的 get()、set() 和 remove() 方法中都加入了检查和清理机制,以确保在访问时能够及时移除那些已经没有强引用的 ThreadLocal 对象关联的值。
ThreadLocal 底层原理的核心在于利用了 Java 弱引用机制以及为每个线程创建了一个自定义的 Map 结构来存放线程局部变量,从而实现了线程间数据的隔离。 同时,它也需要注意内存泄漏的风险,并通过内部逻辑进行了一定程度上的优化处理。
四、场景应用
一个简单的 ThreadLocal 使用案例,将使用它来为每个线程维护一个独立的计数器。
-
创建了一个 ThreadLocal 变量 threadLocalCounter,用于存储每个线程的独立计数器。
-
每个线程在开始执行时都会设置自己的计数器初始值,并在循环中不断更新这个计数器。
-
每个线程都有各自的 ThreadLocal 变量副本,所以线程之间的计数器是相互独立的,互不影响。
-
主线程尝试访问 threadLocalCounter 的值,主线程没有设置过该变量,输出的是默认值 null。
public class ThreadLocalSimpleExample {
// 创建一个 ThreadLocal 变量用于存储每个线程的计数器
public static final ThreadLocal<Integer> threadLocalCounter = new ThreadLocal<>();
public static void main(String[] args) {
// 创建两个线程
Thread thread1 = new Thread(() -> {
// 为当前线程设置初始计数值
threadLocalCounter.set(0);
// 在线程中执行任务并更新计数器
for (int i = 0; i < 5; i++) {
int currentCount = threadLocalCounter.get();
threadLocalCounter.set(currentCount + 1);
System.out.println("Thread 1: Counter value is " + threadLocalCounter.get());
}
});
Thread thread2 = new Thread(() -> {
// 为当前线程设置初始计数值
threadLocalCounter.set(0);
// 在线程中执行任务并更新计数器
for (int i = 0; i < 3; i++) {
int currentCount = threadLocalCounter.get();
threadLocalCounter.set(currentCount + 1);
System.out.println("Thread 2: Counter value is " + threadLocalCounter.get());
}
});
// 启动两个线程
thread1.start();
thread2.start();
// 等待所有线程结束
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 主线程尝试访问计数器(主线程没有设置过计数器,因此会返回默认值 null)
System.out.println("Main Thread: Counter value is " + threadLocalCounter.get());
}
}