Java ThreadLocal 类
最后修改时间:2025 年 4 月 13 日
java.lang.ThreadLocal 类提供线程局部变量。这些变量与普通变量不同,因为每个线程都有其自身独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段。
当您想维护每个线程的状态而无需同步时,ThreadLocal 非常有用。常见的用例包括存储用户会话、事务上下文或需要与其他线程隔离的其他特定于线程的数据。
ThreadLocal 基础
ThreadLocal 通过为每个线程创建一个单独的变量副本来提供线程隔离。主要方法包括 get、set、remove 和 initialValue。
public class ThreadLocal<T> {
public T get() {...}
public void set(T value) {...}
public void remove() {...}
protected T initialValue() {...}
}
上面的代码显示了 ThreadLocal 提供的主要方法。泛型类型 T 表示线程局部变量中存储的值的类型。
基本 ThreadLocal 示例
此示例演示了 ThreadLocal 的基本用法。我们创建一个简单的计数器,为每个线程维护单独的计数。
package com.zetcode;
public class ThreadLocalExample {
private static final ThreadLocal<Integer> threadLocalCounter =
ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
Runnable task = () -> {
int counter = threadLocalCounter.get();
threadLocalCounter.set(counter + 1);
System.out.println(Thread.currentThread().getName() +
": " + threadLocalCounter.get());
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
Thread thread3 = new Thread(task);
thread1.start();
thread2.start();
thread3.start();
}
}
每个线程都获得自己的计数器副本,初始化为 0。当一个线程增加其计数器时,它不会影响其他线程中的计数器。输出显示每个线程独立维护自己的计数。
带有 InitialValue 的 ThreadLocal
此示例显示了如何覆盖 initialValue 方法,以便为每个线程的变量副本提供自定义初始值。
package com.zetcode;
public class ThreadLocalInitialValue {
private static final ThreadLocal<String> threadLocal =
new ThreadLocal<>() {
@Override
protected String initialValue() {
return "Initial-" + Thread.currentThread().getName();
}
};
public static void main(String[] args) {
Runnable task = () -> {
System.out.println(Thread.currentThread().getName() +
": " + threadLocal.get());
threadLocal.set("Modified-" + Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() +
": " + threadLocal.get());
};
new Thread(task, "Thread-1").start();
new Thread(task, "Thread-2").start();
}
}
每个线程都以包含线程名称的自己的初始值开始。然后,我们在每个线程中独立修改该值。输出表明,一个线程中的更改不会影响其他线程的值。
Web 应用程序中的 ThreadLocal
ThreadLocal 通常用于 Web 应用程序中,以存储每个请求的数据,例如用户会话或事务上下文。此示例模拟 Web 请求处理程序。
package com.zetcode;
public class WebRequestSimulator {
private static final ThreadLocal<String> userSession = new ThreadLocal<>();
static class RequestHandler implements Runnable {
private final String userId;
RequestHandler(String userId) {
this.userId = userId;
}
@Override
public void run() {
userSession.set("Session-" + userId);
try {
System.out.println(Thread.currentThread().getName() +
" processing request for user: " + userId);
System.out.println("Session ID: " + userSession.get());
Thread.sleep(100); // Simulate request processing
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
userSession.remove(); // Clean up
}
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 5; i++) {
executor.execute(new RequestHandler("User" + i));
}
executor.shutdown();
}
}
此示例模拟在线程池中处理 Web 请求。每个请求都将其会话存储在 ThreadLocal 中。finally 块确保使用 remove 进行清理,以防止线程池中的内存泄漏。
带有 SimpleDateFormat 的 ThreadLocal
SimpleDateFormat 不是线程安全的,这使得 ThreadLocal 成为跨多个线程共享它的理想解决方案。此示例演示了此模式。
package com.zetcode;
public class DateFormatExample {
private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
static class DatePrinter implements Runnable {
private final Date date;
DatePrinter(Date date) {
this.date = date;
}
@Override
public void run() {
String formattedDate = dateFormat.get().format(date);
System.out.println(Thread.currentThread().getName() +
": " + formattedDate);
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
Date today = new Date();
for (int i = 0; i < 5; i++) {
executor.execute(new DatePrinter(today));
}
executor.shutdown();
}
}
每个线程都获得自己的 SimpleDateFormat 实例,从而避免了同步问题。这比为每个格式化操作创建一个新的 SimpleDateFormat 或同步对共享实例的访问更有效。
用于上下文传递的 ThreadLocal
ThreadLocal 可用于通过多个方法调用传递上下文,而无需显式传递参数。此示例显示了一个事务上下文。
package com.zetcode;
public class TransactionContext {
private static final ThreadLocal<String> transactionId = new ThreadLocal<>();
static void startTransaction(String id) {
transactionId.set(id);
System.out.println("Started transaction: " + id);
}
static void processTransaction() {
System.out.println("Processing transaction: " + transactionId.get());
}
static void commitTransaction() {
System.out.println("Committing transaction: " + transactionId.get());
transactionId.remove();
}
static class TransactionTask implements Runnable {
private final String id;
TransactionTask(String id) {
this.id = id;
}
@Override
public void run() {
try {
startTransaction(id);
processTransaction();
commitTransaction();
} catch (Exception e) {
transactionId.remove(); // Clean up on error
}
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(new TransactionTask("TXN-001"));
executor.execute(new TransactionTask("TXN-002"));
executor.shutdown();
}
}
事务 ID 存储在 ThreadLocal 中,并由多个方法访问,而无需作为参数传递。每个线程都维护自己的事务上下文,并且在事务完成后,ID 会被正确清理。
带有 InheritableThreadLocal 的 ThreadLocal
InheritableThreadLocal 允许子线程从父线程继承值。此示例演示了此行为。
package com.zetcode;
public class InheritableThreadLocalExample {
private static final ThreadLocal<String> regularThreadLocal =
new ThreadLocal<>();
private static final InheritableThreadLocal<String> inheritableThreadLocal =
new InheritableThreadLocal<>();
public static void main(String[] args) {
regularThreadLocal.set("Parent Thread Value");
inheritableThreadLocal.set("Parent Thread Value");
Thread childThread = new Thread(() -> {
System.out.println("Regular ThreadLocal in child: " +
regularThreadLocal.get());
System.out.println("InheritableThreadLocal in child: " +
inheritableThreadLocal.get());
});
childThread.start();
}
}
子线程可以通过 InheritableThreadLocal 访问在父线程中设置的值,但不能通过常规 ThreadLocal 访问。当您需要将上下文传递给子线程,同时保持线程安全时,这非常有用。
ThreadLocal 内存泄漏预防
如果使用不当,ThreadLocal 可能会导致内存泄漏,尤其是在线程池中。此示例显示了正确的清理。
package com.zetcode;
public class ThreadLocalCleanup {
private static final ThreadLocal<byte[]> bigData = new ThreadLocal<>();
static class Task implements Runnable {
@Override
public void run() {
try {
// Store large data in ThreadLocal
bigData.set(new byte[10 * 1024 * 1024]); // 10MB
System.out.println("Stored data in " +
Thread.currentThread().getName());
} finally {
// Critical: remove to prevent memory leak
bigData.remove();
System.out.println("Cleaned up in " +
Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
for (int i = 0; i < 5; i++) {
executor.execute(new Task());
}
executor.shutdown();
}
}
此示例演示了 ThreadLocal 变量的正确清理。即使发生异常,finally 块也能确保删除。如果没有此清理,线程池线程将保留对大型字节数组的引用,从而导致内存泄漏。
来源
在本文中,我们通过实际示例介绍了 Java ThreadLocal 类。ThreadLocal 是一个强大的工具,用于管理每个线程的状态而无需同步,但必须小心使用,以避免内存泄漏。
作者
列出所有Java教程。