Java并发编程之ThreadLocal详解
什么是ThreadLocal?
ThreadLocal 是 Java 中用于实现线程本地变量的机制,它提供了一种让每个线程独立管理变量的方式。也就是说,ThreadLocal 可以为每个线程创建一个单独的变量副本,各个线程之间互不干扰。这种机制在多线程编程中很常见,它可以解决多线程条件下数据共享和线程安全的问题。
ThreadLocal 的使用方法
ThreadLocal 没有公开的构造方法,它通过静态方法 ThreadLocal.withInitial()
来创建一个变量副本。withInitial() 方法接受一个 Supplier 接口类型的参数,该接口的实现方法返回一个初始值。
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)
例如,创建一个 ThreadLocal 对象并初始化为字符串类型的空值。
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "");
然后,就可以通过 get() 方法来取出当前线程保存的副本。如果当前线程没有保存任何副本,那么 get() 方法会通过调用 Supplier 的 get() 方法来获取一个初始值副本并保存。
String value = threadLocal.get();
反之,如果已有副本,则直接返回该副本。
String value = threadLocal.get();
还可以通过 set() 方法来设置当前线程保存的副本。
threadLocal.set("value");
当然,如果不再需要当前线程保存的副本,或者需要清理线程保存的全部副本,也可以调用remove方法。
threadLocal.remove();
ThreadLocal 的基本应用场景
完全解耦
ThreadLocal 可以让所有的线程访问不同的对象副本,从而实现了完全解耦。不同线程所访问的是不同的对象副本,彼此之间对该对象的修改不会互相影响,从而避免了共享对象的多线程并发访问冲突。
例如,在 Web 应用中,使用 ThreadLocal 可以在每个请求处理的时候,为该请求单独准备一个数据库连接(或者一个 DAO 对象),以避免不同请求之间发生冲突。
用于线程间的状态传递
ThreadLocal 还可以用于线程之间状态传递,每个线程保存自己的状态,然后将状态传递给其他线程。例如:
public class Demo {
private static final ThreadLocal<Integer> THREAD_LOCAL = new ThreadLocal<>();
public static void main(String[] args) {
THREAD_LOCAL.set(1);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": " + THREAD_LOCAL.get());
}, "thread-1").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": " + THREAD_LOCAL.get());
}, "thread-2").start();
}
}
在上面的示例中,我们先将 THREAD_LOCAL 的值设置为 1,然后分别启动了两个线程,这两个线程都通过 ThreadLocal 来访问 THREAD_LOCAL 的值。可以看出,它们都是访问到的各自的副本,而不是一个共享变量。因此,它们输出的结果也都是不相同的。
ThreadLocal 需要注意的问题
内存泄漏
由于 ThreadLocal 需要为每个线程单独创建一个副本,如果不及时清理这些副本,就很容易导致内存泄漏问题。
内存泄漏的产生是因为由于 ThreadLocalMap 中的 Entry 引用了ThreadLocal,而 ThreadLocal 又持有 Entry 的强引用,ThreadLocalMap 的生命周期与 Thread 一样长,而 ThreadLocal 却没有及时被回收,就会导致内存泄漏。
public class Demo {
private static final ThreadLocal<List<Integer>> THREAD_LOCAL = new ThreadLocal<>();
public static void main(String[] args) {
THREAD_LOCAL.set(new ArrayList<>());
// 模拟业务处理,假设需要往 THREAD_LOCAL 中添加多个值
for (int i = 0; i < 1000000; i++) {
THREAD_LOCAL.get().add(i);
}
THREAD_LOCAL.remove(); // 手动清理 ThreadLocal 对象
}
}
在上面的示例中,我们在 main 方法中先将 THREAD_LOCAL 设置为一个新的 ArrayList 对象,并在随后的代码中不断向其中添加元素,模拟业务处理。由于这里没有手动清理 THREAD_LOCAL 对象,因此可能会导致内存泄漏。为了避免这种情况,我们可以在每个线程处理完业务后,手动清理 ThreadLocal 对象。
线程池中的 ThreadLocal 泄漏
在使用线程池的时候,如果某个 ThreadLocal 没有被及时清理,就有可能导致该 ThreadLocal 在线程池中的所有线程中复用,有可能导致线程不可重入、线程上下文状态混乱等问题。
总结
ThreadLocal 是解决并发问题的有效手段之一,它为多线程编程提供了一种应对策略,为我们避免了多线程数据共享和线程安全的问题。通过了解 ThreadLocal 的基本使用方法以及注意事项,我们可以在实际开发中更好地运用它。
示例
示例一 将 MySQL 连接对象存储在 ThreadLocal 中
public class ConnectionManager {
private static final ThreadLocal<Connection> CONNECTION_HOLDER = ThreadLocal.withInitial(() -> {
try {
return DriverManager.getConnection(DBConfig.getUrl(), DBConfig.getUsername(), DBConfig.getPassword());
} catch (SQLException e) {
logger.error("获取数据库连接失败:{}", e.getMessage());
}
return null;
});
public static Connection getConnection() {
return CONNECTION_HOLDER.get();
}
public static void closeConnection() {
Connection connection = CONNECTION_HOLDER.get();
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
logger.error("关闭数据库连接失败:{}", e.getMessage());
} finally {
CONNECTION_HOLDER.remove();
}
}
}
}
在上面的示例中,我们使用 ThreadLocal 来保证每个线程获取到的 Connection 对象都是独立的。在 getConnection() 方法中,我们通过 CONNECTION_HOLDER 获取当前线程保存的 Connection 对象,如果不存在则创建一个新的对象并返回;在 closeConnection() 方法中,我们先尝试关闭 Connection 对象,然后再及时清理 ThreadLocal 对象,以避免内存泄漏。
示例二 使用 ThreadLocal 保存请求信息
public class RequestContext {
public static final ThreadLocal<String> USER_ID = ThreadLocal.withInitial(() -> null);
public static final ThreadLocal<String> REQUEST_ID = ThreadLocal.withInitial(() -> null);
public static final ThreadLocal<Date> REQUEST_TIME = ThreadLocal.withInitial(() -> null);
// ...
}
在上面的示例中,我们通过 ThreadLocal 来保存请求信息,USER_ID 表示用户 ID,REQUEST_ID 表示请求 ID,REQUEST_TIME 表示请求时间。通过使用 ThreadLocal 可以保证每个线程获取到的请求信息都是独立的,这对于多线程环境中的请求处理非常重要。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java并发编程之ThreadLocal详解 - Python技术站