ThreadLocal是什么
ThreadLocal
,即线程本地变量。如果你创建了一个ThreadLocal
变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是在操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了并发场景下的线程安全问题。
ThreadLocal
的主要作用如下:
- 线程隔离:
ThreadLocal
可以实现线程间的数据隔离。每个线程可以独立地使用和修改自己的线程局部变量,而不会与其他线程的变量产生冲突。这对于多线程环境下的共享数据访问和线程安全性非常有用。 - 线程上下文传递:
ThreadLocal
可以用于在线程之间传递上下文信息。例如,在一个使用线程池的应用中,可以通过ThreadLocal
在任务提交和执行之间传递一些上下文信息,而不需要显式地将上下文作为参数传递。 - 减少同步开销:使用
ThreadLocal
可以减少线程间进行同步的需求。由于每个线程都拥有自己的变量副本,不需要进行额外的同步操作,因此可以减少锁的竞争和同步开销,提高并发性能。 - 线程封闭性:
ThreadLocal
可以实现数据的线程封闭,即将某些数据限定在特定的线程中使用。这对于一些需要临时保存线程私有数据的场景非常有用,避免了数据被其他线程访问和修改的风险。
为什么使用ThreadLocal
并发场景下,会存在多个线程同时修改一个共享变量的场景。这就可能会出现线性安全问题。
为了解决线性安全问题,可以用加锁的方式,比如使用synchronized
或者Lock
。但是加锁的方式,可能会导致系统变慢。加锁示意图如下:
还有另外一种方案,就是使用空间换时间的方式,即使用ThreadLocal
。使用ThreadLocal
类访问共享变量时,会在每个线程的本地,都保存一份共享变量的拷贝副本。多线程对共享变量修改时,实际上操作的是这个变量副本,从而保证线性安全。
ThreadLocal使用案例
-
声明一个
ThreadLocal
类型的变量:private static ThreadLocal<T> threadLocal = new ThreadLocal<>();
其中
T
是存储在ThreadLocal
中的值的类型。 -
使用
ThreadLocal
类的set()
方法设置值:threadLocal.set(value);
这将把
value
存储在当前线程的ThreadLocal
实例中。 -
使用
ThreadLocal
类的get()
方法获取值:T value = threadLocal.get();
这将返回当前线程的
ThreadLocal
实例中存储的值。 -
使用
ThreadLocal
类的remove()
方法清除值(可选):threadLocal.remove();
这将从当前线程的
ThreadLocal
实例中移除值。 -
最后,在不再需要
ThreadLocal
对象时,应调用remove()
方法来清理资源:threadLocal.remove();
这样可以避免潜在的内存泄漏问题。
需要注意的是,ThreadLocal
的 set()
和 get()
方法都是针对当前线程的操作。因此,在使用 ThreadLocal
时,应确保在同一线程范围内使用相同的 ThreadLocal
对象。这样才能保证在同一线程中的多个方法或代码段中共享同一个 ThreadLocal
实例。
此外,可以为 ThreadLocal
提供初始值和默认值。例如,可以使用 ThreadLocal
的构造函数或 initialValue()
方法来设置初始值:
private static ThreadLocal<T> threadLocal = new ThreadLocal<T>() {
@Override
protected T initialValue() {
return initialValue;
}
};
或者,可以在声明 ThreadLocal
变量时使用 lambada 表达式提供默认值:
private static ThreadLocal<T> threadLocal = ThreadLocal.withInitial(() -> defaultValue);
下面是一个简单的案例
public class ThreadLocalExample {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 在主线程中设置线程局部变量
threadLocal.set("Main Thread");
// 创建并启动子线程
Thread childThread = new Thread(() -> {
// 在子线程中获取并修改线程局部变量
String value = threadLocal.get();
System.out.println("子线程值: " + value);
threadLocal.set("Child Thread");
// 获取修改后的线程局部变量
String modifiedValue = threadLocal.get();
System.out.println("子线程值:" + modifiedValue);
// 清理线程局部变量
threadLocal.remove();
});
childThread.start();
// 在主线程中获取线程局部变量
String mainThreadValue = threadLocal.get();
System.out.println("主线程值:" + mainThreadValue);
// 清理线程局部变量
threadLocal.remove();
}
}
// 运行结果为
主线程值:Main Thread
子线程值: null
子线程修改后的值:Child Thread
ThreadLocal的应用场景和使用注意点
ThreadLocal
的很重要一个注意点,就是使用完,要手动调用remove()
。
而ThreadLocal
的应用场景主要有以下这几种:
- 使用日期工具类,当用到
SimpleDateFormat
,使用ThreadLocal
保证线性安全 - 全局存储用户信息(用户信息存入
ThreadLocal
,那么当前线程在任何地方需要时,都可以使用) - 保证同一个线程,获取的数据库连接
Connection
是同一个,使用ThreadLocal
来解决线程安全的问题 - 使用
MDC
保存日志信息。
不适用场景:
-
ThreadLocal
在高并发场景下可能存在性能问题。当多个线程同时修改ThreadLocal
的值时,需要进行加锁操作,可能导致线程竞争和性能下降。 -
ThreadLocal
的作用范围仅限于当前线程。如果需要在不同的线程之间传递数据ThreadLocal
将无法起到作用 -
ThreadLocal
在使用过程中需要特别注意内存泄漏问题。如果没有及时清除ThreadLocal
的值或者线程一直处于活跃状态,可能导致ThreadLocal
对象无法被垃圾回收,进而造成内存泄漏。