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教程。