ThreadLocal 详解

[复制链接]
发表于 2025-9-10 16:30:04 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
1. ThreadLocal 简介

1.1 什么是 ThreadLocal?

ThreadLocal 是 Java 提供的一个类,它提供了线程本地变量的功能。这些变量与平凡变量差别,每个线程访问 ThreadLocal 变量时,都会有自己独立的、初始化过的变量副本,其他线程无法访问。简而言之,ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
ThreadLocal 的主要特点:


  • 每个线程都有自己的变量副本,相互互不影响
  • 适合在多线程环境下处理线程安全问题
  • 简化了并发编程中的同步操纵
  • 适合存储与线程相关的状态信息
1.2 为什么需要 ThreadLocal?

在多线程环境下,我们经常需要处理线程安全问题。通常有两种主要的方式:

  • 同步(Synchronization):使用 synchronized 关键字或 Lock 接口实现多线程之间的同步,确保同一时刻只有一个线程能够访问共享资源。
  • 线程本地存储(ThreadLocal):为每个线程创建独立的变量副本,避免共享变量,从而规避线程安全问题。
ThreadLocal 适用于以了局景:


  • 当某个数据需要被某个线程独享时
  • 当某个数据的生命周期与线程的生命周期相同时
  • 需要避免线程安全问题,但又不想使用同步机制(因为同步会导致性能开销)时
2. ThreadLocal 的工作原理

2.1 ThreadLocal 的内部布局

ThreadLocal 的工作原理看似复杂,但理解起来并不难。下面是其基本原理:

  • Thread 类中有一个成员变量 ThreadLocalMap,它是一个 Map 布局
  • ThreadLocalMap 的 key 是 ThreadLocal 对象的弱引用,value 是具体的值
  • 当调用 ThreadLocal 的 set(T value) 方法时,会先获取当前线程,然后将值存储在当前线程的 ThreadLocalMap 中
  • 当调用 ThreadLocal 的 get() 方法时,会从当前线程的 ThreadLocalMap 中获取值
下面是一个简化的工作原理图:
  1. Thread 对象
  2.    ├── ThreadLocalMap
  3.        ├── entry1: <ThreadLocal1 引用, 值1>
  4.        ├── entry2: <ThreadLocal2 引用, 值2>
  5.        └── ...
复制代码
2.2 ThreadLocal 的代码实现原理

我们来看看 ThreadLocal 的关键方法实现原理(简化版):
set 方法
  1. public void set(T value) {
  2.     // 获取当前线程
  3.     Thread t = Thread.currentThread();
  4.     // 获取当前线程的 ThreadLocalMap
  5.     ThreadLocalMap map = getMap(t);
  6.     // 如果 map 存在,则直接设置值
  7.     if (map != null)
  8.         map.set(this, value);
  9.     else
  10.         // 否则创建 map 并设置值
  11.         createMap(t, value);
  12. }
复制代码
get 方法
  1. public T get() {
  2.     // 获取当前线程
  3.     Thread t = Thread.currentThread();
  4.     // 获取当前线程的 ThreadLocalMap
  5.     ThreadLocalMap map = getMap(t);
  6.     // 如果 map 存在
  7.     if (map != null) {
  8.         // 获取与当前 ThreadLocal 对象关联的 Entry
  9.         ThreadLocalMap.Entry e = map.getEntry(this);
  10.         if (e != null) {
  11.             @SuppressWarnings("unchecked")
  12.             // 返回值
  13.             T result = (T)e.value;
  14.             return result;
  15.         }
  16.     }
  17.     // 如果 map 不存在或 entry 不存在,则返回初始值
  18.     return setInitialValue();
  19. }
复制代码
remove 方法
  1. public void remove() {
  2.     // 获取当前线程的 ThreadLocalMap
  3.     ThreadLocalMap m = getMap(Thread.currentThread());
  4.     // 如果 map 存在,则从中删除当前 ThreadLocal 对应的 entry
  5.     if (m != null)
  6.         m.remove(this);
  7. }
复制代码
3. ThreadLocal 的使用方法

3.1 创建 ThreadLocal 对象

创建 ThreadLocal 对象非常简单,只需使用泛型指定存储的数据范例:
  1. // 创建一个存储 Integer 类型的 ThreadLocal
  2. ThreadLocal<Integer> threadLocalInt = new ThreadLocal<>();
  3. // 创建一个存储 String 类型的 ThreadLocal
  4. ThreadLocal<String> threadLocalString = new ThreadLocal<>();
  5. // 创建一个存储自定义对象类型的 ThreadLocal
  6. ThreadLocal<User> threadLocalUser = new ThreadLocal<>();
复制代码
3.2 设置初始值

ThreadLocal 提供了两种设置初始值的方式:
方式一:重写 initialValue 方法
  1. ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
  2.     @Override
  3.     protected Integer initialValue() {
  4.         return 0; // 设置默认值为 0
  5.     }
  6. };
复制代码
方式二:使用 withInitial 静态方法(Java 8 及以上)
  1. ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
复制代码
方式三:在初次使用前设置值
  1. ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
  2. threadLocal.set(0);
复制代码
3.3 基本操纵:get、set、remove

ThreadLocal 有三个核心方法:


  • set(T value):设置当前线程的线程局部变量值
  • get():获取当前线程的线程局部变量值
  • remove():移除当前线程的线程局部变量
示例:
  1. public class ThreadLocalExample {
  2.     // 创建一个 ThreadLocal 对象
  3.     private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
  4.     public static void main(String[] args) {
  5.         // 主线程设置值
  6.         threadLocal.set("Main Thread Value");
  7.         
  8.         // 获取值
  9.         System.out.println("Main Thread: " + threadLocal.get());
  10.         
  11.         // 创建一个新线程
  12.         Thread thread = new Thread(() -> {
  13.             // 新线程中获取值(初始为 null,因为每个线程都有独立的副本)
  14.             System.out.println("New Thread Initially: " + threadLocal.get());
  15.             
  16.             // 新线程设置自己的值
  17.             threadLocal.set("New Thread Value");
  18.             
  19.             // 再次获取值
  20.             System.out.println("New Thread After Setting: " + threadLocal.get());
  21.             
  22.             // 移除值
  23.             threadLocal.remove();
  24.             
  25.             // 移除后再获取值(应该为 null 或初始值)
  26.             System.out.println("New Thread After Removal: " + threadLocal.get());
  27.         });
  28.         
  29.         thread.start();
  30.         
  31.         try {
  32.             thread.join(); // 等待新线程执行完成
  33.         } catch (InterruptedException e) {
  34.             e.printStackTrace();
  35.         }
  36.         
  37.         // 主线程的值不受新线程影响
  38.         System.out.println("Main Thread Again: " + threadLocal.get());
  39.         
  40.         // 最后,主线程也要移除值,防止内存泄漏
  41.         threadLocal.remove();
  42.     }
  43. }
复制代码
输出示例:
  1. Main Thread: Main Thread Value
  2. New Thread Initially: null
  3. New Thread After Setting: New Thread Value
  4. New Thread After Removal: null
  5. Main Thread Again: Main Thread Value
复制代码
3.4 使用 InheritableThreadLocal

如果希望子线程能继承父线程的 ThreadLocal 变量,可以使用 InheritableThreadLocal:
  1. public class InheritableThreadLocalExample {
  2.     // 创建一个 InheritableThreadLocal 对象
  3.     private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
  4.     public static void main(String[] args) {
  5.         // 主线程设置值
  6.         inheritableThreadLocal.set("Main Thread Value");
  7.         
  8.         // 创建一个新线程
  9.         Thread thread = new Thread(() -> {
  10.             // 新线程继承了父线程的值
  11.             System.out.println("New Thread: " + inheritableThreadLocal.get());
  12.             
  13.             // 修改值不会影响父线程
  14.             inheritableThreadLocal.set("New Thread Modified Value");
  15.             System.out.println("New Thread Modified: " + inheritableThreadLocal.get());
  16.         });
  17.         
  18.         thread.start();
  19.         
  20.         try {
  21.             thread.join(); // 等待新线程执行完成
  22.         } catch (InterruptedException e) {
  23.             e.printStackTrace();
  24.         }
  25.         
  26.         // 主线程的值不受子线程修改的影响
  27.         System.out.println("Main Thread Again: " + inheritableThreadLocal.get());
  28.         
  29.         // 最后,移除值
  30.         inheritableThreadLocal.remove();
  31.     }
  32. }
复制代码
输出示例:
  1. New Thread: Main Thread Value
  2. New Thread Modified: New Thread Modified Value
  3. Main Thread Again: Main Thread Value
复制代码
4. ThreadLocal 的应用场景

ThreadLocal 在现实开发中有很多应用场景,下面是一些常见的例子:
4.1 在多线程环境下保存用户信息

在 Web 应用中,通常需要在整个哀求处理过程中转达用户信息(如用户ID、用户名等)。使用 ThreadLocal 可以避免在每个方法中都转达用户参数。
  1. public class UserContext {
  2.     private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
  3.    
  4.     public static void setUser(User user) {
  5.         userThreadLocal.set(user);
  6.     }
  7.    
  8.     public static User getUser() {
  9.         return userThreadLocal.get();
  10.     }
  11.    
  12.     public static void clear() {
  13.         userThreadLocal.remove();
  14.     }
  15. }
  16. // 使用示例
  17. public class UserService {
  18.     public void processUser(String userId) {
  19.         // 从数据库获取用户
  20.         User user = getUserFromDB(userId);
  21.         
  22.         // 设置到 ThreadLocal
  23.         UserContext.setUser(user);
  24.         
  25.         // 其他方法可以直接获取用户信息,无需传参
  26.         businessMethod1();
  27.         businessMethod2();
  28.         
  29.         // 操作完成后清理 ThreadLocal
  30.         UserContext.clear();
  31.     }
  32.    
  33.     private void businessMethod1() {
  34.         // 直接获取用户信息
  35.         User user = UserContext.getUser();
  36.         System.out.println("Business Method 1 for user: " + user.getName());
  37.     }
  38.    
  39.     private void businessMethod2() {
  40.         // 直接获取用户信息
  41.         User user = UserContext.getUser();
  42.         System.out.println("Business Method 2 for user: " + user.getName());
  43.     }
  44.    
  45.     private User getUserFromDB(String userId) {
  46.         // 模拟从数据库获取用户
  47.         return new User(userId, "User" + userId);
  48.     }
  49. }
  50. class User {
  51.     private String id;
  52.     private String name;
  53.    
  54.     public User(String id, String name) {
  55.         this.id = id;
  56.         this.name = name;
  57.     }
  58.    
  59.     public String getId() {
  60.         return id;
  61.     }
  62.    
  63.     public String getName() {
  64.         return name;
  65.     }
  66. }
复制代码
4.2 数据库毗连管理

ThreadLocal 可以用于管理数据库毗连,为每个线程提供独立的数据库毗连,避免多线程争用同一毗连导致的问题。
  1. public class ConnectionManager {
  2.     private static final ThreadLocal<Connection> connectionHolder = ThreadLocal.withInitial(() -> {
  3.         try {
  4.             return DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
  5.         } catch (SQLException e) {
  6.             throw new RuntimeException("创建数据库连接失败", e);
  7.         }
  8.     });
  9.    
  10.     public static Connection getConnection() {
  11.         return connectionHolder.get();
  12.     }
  13.    
  14.     public static void closeConnection() {
  15.         Connection conn = connectionHolder.get();
  16.         if (conn != null) {
  17.             try {
  18.                 conn.close();
  19.             } catch (SQLException e) {
  20.                 // 处理关闭连接异常
  21.             }
  22.         }
  23.         connectionHolder.remove();  // 不要忘记移除
  24.     }
  25. }
  26. // 使用示例
  27. public class DatabaseService {
  28.     public void performDatabaseOperations() {
  29.         try {
  30.             // 获取当前线程的数据库连接
  31.             Connection conn = ConnectionManager.getConnection();
  32.             
  33.             // 使用连接执行 SQL 操作
  34.             try (Statement stmt = conn.createStatement()) {
  35.                 // 执行查询
  36.                 ResultSet rs = stmt.executeQuery("SELECT * FROM users");
  37.                 while (rs.next()) {
  38.                     System.out.println("User: " + rs.getString("username"));
  39.                 }
  40.             }
  41.         } catch (SQLException e) {
  42.             e.printStackTrace();
  43.         } finally {
  44.             // 操作完成后关闭并清除连接
  45.             ConnectionManager.closeConnection();
  46.         }
  47.     }
  48. }
复制代码
4.3 简化参数转达

ThreadLocal 可以在调用链中转达参数,避免在每个方法中都转达相同的参数。
  1. public class RequestContext {
  2.     private static final ThreadLocal<RequestData> requestThreadLocal = new ThreadLocal<>();
  3.    
  4.     public static void setRequestData(RequestData requestData) {
  5.         requestThreadLocal.set(requestData);
  6.     }
  7.    
  8.     public static RequestData getRequestData() {
  9.         return requestThreadLocal.get();
  10.     }
  11.    
  12.     public static void clear() {
  13.         requestThreadLocal.remove();
  14.     }
  15. }
  16. class RequestData {
  17.     private String requestId;
  18.     private String clientIp;
  19.     private String userAgent;
  20.    
  21.     // 构造函数、getter和setter省略...
  22. }
  23. // 使用示例
  24. public class RequestProcessor {
  25.     public void processRequest(HttpRequest request) {
  26.         // 解析请求信息
  27.         RequestData requestData = new RequestData();
  28.         requestData.setRequestId(generateRequestId());
  29.         requestData.setClientIp(request.getClientIp());
  30.         requestData.setUserAgent(request.getUserAgent());
  31.         
  32.         // 设置到 ThreadLocal
  33.         RequestContext.setRequestData(requestData);
  34.         
  35.         try {
  36.             // 处理请求的各个阶段
  37.             validateRequest();
  38.             authenticateUser();
  39.             processBusinessLogic();
  40.             generateResponse();
  41.         } finally {
  42.             // 清理 ThreadLocal
  43.             RequestContext.clear();
  44.         }
  45.     }
  46.    
  47.     private void validateRequest() {
  48.         RequestData data = RequestContext.getRequestData();
  49.         System.out.println("Validating request: " + data.getRequestId());
  50.         // 验证逻辑...
  51.     }
  52.    
  53.     private void authenticateUser() {
  54.         RequestData data = RequestContext.getRequestData();
  55.         System.out.println("Authenticating user for request: " + data.getRequestId());
  56.         // 认证逻辑...
  57.     }
  58.    
  59.     private void processBusinessLogic() {
  60.         RequestData data = RequestContext.getRequestData();
  61.         System.out.println("Processing business logic for request: " + data.getRequestId());
  62.         // 业务逻辑...
  63.     }
  64.    
  65.     private void generateResponse() {
  66.         RequestData data = RequestContext.getRequestData();
  67.         System.out.println("Generating response for request: " + data.getRequestId());
  68.         // 生成响应...
  69.     }
  70.    
  71.     private String generateRequestId() {
  72.         return UUID.randomUUID().toString();
  73.     }
  74. }
复制代码
4.4 事务管理

在涉及事务的应用中,ThreadLocal 可以用于跟踪和管理事务状态。
  1. public class TransactionManager {
  2.     private static final ThreadLocal<Transaction> transactionThreadLocal = new ThreadLocal<>();
  3.    
  4.     public static void beginTransaction() {
  5.         Transaction transaction = new Transaction();
  6.         transaction.begin();
  7.         transactionThreadLocal.set(transaction);
  8.     }
  9.    
  10.     public static Transaction getCurrentTransaction() {
  11.         return transactionThreadLocal.get();
  12.     }
  13.    
  14.     public static void commitTransaction() {
  15.         Transaction transaction = transactionThreadLocal.get();
  16.         if (transaction != null) {
  17.             transaction.commit();
  18.             transactionThreadLocal.remove();
  19.         }
  20.     }
  21.    
  22.     public static void rollbackTransaction() {
  23.         Transaction transaction = transactionThreadLocal.get();
  24.         if (transaction != null) {
  25.             transaction.rollback();
  26.             transactionThreadLocal.remove();
  27.         }
  28.     }
  29. }
  30. class Transaction {
  31.     private String id;
  32.    
  33.     public Transaction() {
  34.         this.id = UUID.randomUUID().toString();
  35.     }
  36.    
  37.     public void begin() {
  38.         System.out.println("Transaction " + id + " started");
  39.     }
  40.    
  41.     public void commit() {
  42.         System.out.println("Transaction " + id + " committed");
  43.     }
  44.    
  45.     public void rollback() {
  46.         System.out.println("Transaction " + id + " rolled back");
  47.     }
  48. }
  49. // 使用示例
  50. public class TransactionExample {
  51.     public void performBusinessOperation() {
  52.         try {
  53.             // 开始事务
  54.             TransactionManager.beginTransaction();
  55.             
  56.             // 执行数据库操作 1
  57.             updateTableA();
  58.             
  59.             // 执行数据库操作 2
  60.             updateTableB();
  61.             
  62.             // 提交事务
  63.             TransactionManager.commitTransaction();
  64.         } catch (Exception e) {
  65.             // 发生异常,回滚事务
  66.             TransactionManager.rollbackTransaction();
  67.             throw e;
  68.         }
  69.     }
  70.    
  71.     private void updateTableA() {
  72.         System.out.println("Updating Table A in transaction: " +
  73.                           TransactionManager.getCurrentTransaction().id);
  74.         // 更新逻辑...
  75.     }
  76.    
  77.     private void updateTableB() {
  78.         System.out.println("Updating Table B in transaction: " +
  79.                           TransactionManager.getCurrentTransaction().id);
  80.         // 更新逻辑...
  81.     }
  82. }
复制代码
5. ThreadLocal 的内存泄漏问题

5.1 内存泄漏的缘故起因

ThreadLocal 使用不当可能导致内存泄漏,主要缘故起因有两点:

  • ThreadLocalMap 的 Entry 是弱引用:ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key,这意味着当没有强引用指向 ThreadLocal 变量时,它会被垃圾采取。但是,对应的 value 是强引用,如果没有手动删除,就无法被采取。
  • 线程池中的线程生命周期很长:在使用线程池的场景下,线程的生命周期可能很长,甚至与应用程序的生命周期一样长。如果不整理 ThreadLocal 变量,那么这些变量会随着线程一直存在于内存中。
下面是一个可能导致内存泄漏的示例:
  1. public class ThreadLocalMemoryLeakExample {
  2.     public static void main(String[] args) {
  3.         ExecutorService executor = Executors.newFixedThreadPool(10);
  4.         
  5.         for (int i = 0; i < 100; i++) {
  6.             executor.execute(new LeakyTask());
  7.         }
  8.         
  9.         executor.shutdown();
  10.     }
  11.    
  12.     static class LeakyTask implements Runnable {
  13.         // 创建一个 ThreadLocal 变量
  14.         private static final ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
  15.         
  16.         @Override
  17.         public void run() {
  18.             // 分配一个大对象到 ThreadLocal 变量
  19.             threadLocal.set(new byte[1024 * 1024]); // 分配 1MB 大小的数组
  20.             
  21.             // 执行一些操作...
  22.             
  23.             // 没有调用 threadLocal.remove(),可能导致内存泄漏
  24.         }
  25.     }
  26. }
复制代码
在上面的例子中,我们创建了一个线程池,并提交了多个任务。每个任务都将一个大对象存储在 ThreadLocal 中,但没有在任务结束时移除该对象。由于线程池中的线程会被重用,这些大对象将一直存在于内存中,导致内存泄漏。
5.2 避免内存泄漏的方法

要避免 ThreadLocal 引起的内存泄漏,应该遵照以下原则:

  • 在不需要 ThreadLocal 变量时调用 remove() 方法
    1. try {
    2.     threadLocal.set(value);
    3.     // 使用 threadLocal...
    4. } finally {
    5.     threadLocal.remove(); // 确保清理
    6. }
    复制代码
  • 使用 try-with-resources 和自界说的 ThreadLocal 资源
    1. public class ThreadLocalScope<T> implements AutoCloseable {
    2.     private final ThreadLocal<T> threadLocal;
    3.    
    4.     public ThreadLocalScope(ThreadLocal<T> threadLocal, T value) {
    5.         this.threadLocal = threadLocal;
    6.         threadLocal.set(value);
    7.     }
    8.    
    9.     @Override
    10.     public void close() {
    11.         threadLocal.remove();
    12.     }
    13. }
    14. // 使用示例
    15. ThreadLocal<String> threadLocal = new ThreadLocal<>();
    16. try (ThreadLocalScope<String> scope = new ThreadLocalScope<>(threadLocal, "value")) {
    17.     // 使用 threadLocal...
    18. } // 自动调用 close() 方法,清理 ThreadLocal
    复制代码
  • 使用第三方库提供的工具类:一些库(如 Spring)提供了整理 ThreadLocal 变量的工具类。
下面是一个修正后的线程池示例:
  1. public class ThreadLocalSafeExample {
  2.     public static void main(String[] args) {
  3.         ExecutorService executor = Executors.newFixedThreadPool(10);
  4.         
  5.         for (int i = 0; i < 100; i++) {
  6.             executor.execute(new SafeTask());
  7.         }
  8.         
  9.         executor.shutdown();
  10.     }
  11.    
  12.     static class SafeTask implements Runnable {
  13.         // 创建一个 ThreadLocal 变量
  14.         private static final ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
  15.         
  16.         @Override
  17.         public void run() {
  18.             try {
  19.                 // 分配一个大对象到 ThreadLocal 变量
  20.                 threadLocal.set(new byte[1024 * 1024]); // 分配 1MB 大小的数组
  21.                
  22.                 // 执行一些操作...
  23.                
  24.             } finally {
  25.                 // 确保清理 ThreadLocal 变量
  26.                 threadLocal.remove();
  27.             }
  28.         }
  29.     }
  30. }
复制代码
6. ThreadLocal 的最佳实践

6.1 何时使用 ThreadLocal

ThreadLocal 并不是解决所有多线程问题的全能药。以下是一些适合使用 ThreadLocal 的场景:


  • 线程隔离的场景:每个线程需要有自己的实例
  • 跨函数转达数据:避免通过参数转达数据
  • 线程安全场景:替代 synchronized 来确保线程安全
不适合使用 ThreadLocal 的场景:


  • 共享数据:如果需要线程之间共享数据,ThreadLocal 不是好的选择
  • 生命周期差别等:如果变量的生命周期与线程的生命周期差别等
  • 频仍创建和销毁线程的场景:可能导致性能问题
6.2 ThreadLocal 使用的注意事项


  • 总是在 finally 块中调用 remove 方法
    1. try {
    2.     threadLocal.set(value);
    3.     // 使用 threadLocal...
    4. } finally {
    5.     threadLocal.remove();
    6. }
    复制代码
  • 为 ThreadLocal 变量使用 private static final 修饰符
    1. private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
    复制代码
  • 优先使用初始化器:尽量使用初始化器设置初始值,避免 NPE(空指针异常):
    1. private static final ThreadLocal<User> userThreadLocal = ThreadLocal.withInitial(() -> new User());
    复制代码
  • 不要在线程池中直接使用不可变 ThreadLocal:线程池中的线程是重用的,以是要确保 ThreadLocal 变量在每次任务结束后都被整理。
  • 避免将 ThreadLocal 变量设置为 null:应该使用 remove() 方法而不是 set(null)。
  • ThreadLocal 变量通常是静态的:ThreadLocal 变量通常声明为静态变量,这样可以确保多个线程访问同一个 ThreadLocal 实例。
6.3 ThreadLocal 工具类示例

下面是一个综合的 ThreadLocal 工具类示例,它遵照了最佳实践:
  1. /**
  2. * ThreadLocal 工具类,提供安全的 ThreadLocal 使用方式
  3. */
  4. public class ThreadLocalContext<T> {
  5.     private final ThreadLocal<T> threadLocal;
  6.    
  7.     /**
  8.      * 创建一个没有初始值的 ThreadLocalContext
  9.      */
  10.     public ThreadLocalContext() {
  11.         this.threadLocal = new ThreadLocal<>();
  12.     }
  13.    
  14.     /**
  15.      * 创建一个带有初始值提供者的 ThreadLocalContext
  16.      */
  17.     public ThreadLocalContext(Supplier<? extends T> supplier) {
  18.         this.threadLocal = ThreadLocal.withInitial(supplier);
  19.     }
  20.    
  21.     /**
  22.      * 获取当前线程的变量值
  23.      */
  24.     public T get() {
  25.         return threadLocal.get();
  26.     }
  27.    
  28.     /**
  29.      * 设置当前线程的变量值
  30.      */
  31.     public void set(T value) {
  32.         threadLocal.set(value);
  33.     }
  34.    
  35.     /**
  36.      * 移除当前线程的变量值
  37.      */
  38.     public void remove() {
  39.         threadLocal.remove();
  40.     }
  41.    
  42.     /**
  43.      * 使用资源并自动清理
  44.      */
  45.     public <R> R withValue(T value, Supplier<R> supplier) {
  46.         set(value);
  47.         try {
  48.             return supplier.get();
  49.         } finally {
  50.             remove();
  51.         }
  52.     }
  53.    
  54.     /**
  55.      * 执行操作并自动清理
  56.      */
  57.     public void withValue(T value, Runnable runnable) {
  58.         set(value);
  59.         try {
  60.             runnable.run();
  61.         } finally {
  62.             remove();
  63.         }
  64.     }
  65. }
  66. // 使用示例
  67. public class ThreadLocalContextExample {
  68.     // 创建用户上下文
  69.     private static final ThreadLocalContext<User> userContext = new ThreadLocalContext<>();
  70.    
  71.     public void processUserRequest(String userId) {
  72.         // 从数据库获取用户
  73.         User user = getUserFromDB(userId);
  74.         
  75.         // 使用 withValue 方法确保自动清理
  76.         userContext.withValue(user, () -> {
  77.             // 处理用户请求
  78.             businessMethod1();
  79.             businessMethod2();
  80.         });
  81.     }
  82.    
  83.     private void businessMethod1() {
  84.         User user = userContext.get();
  85.         System.out.println("Business Method 1 for user: " + user.getName());
  86.     }
  87.    
  88.     private void businessMethod2() {
  89.         User user = userContext.get();
  90.         System.out.println("Business Method 2 for user: " + user.getName());
  91.     }
  92.    
  93.     private User getUserFromDB(String userId) {
  94.         // 模拟从数据库获取用户
  95.         return new User(userId, "User" + userId);
  96.     }
  97. }
复制代码
7. ThreadLocal 与框架集成

很多流行的 Java 框架都使用 ThreadLocal 来管理线程相关的上下文信息。了解这些框架中的 ThreadLocal 使用方式可以资助你更好地使用和调试它们。
7.1 Spring 框架中的 ThreadLocal

Spring 框架在多个地方使用了 ThreadLocal:

  • 哀求上下文:RequestContextHolder 使用 ThreadLocal 存储当前哀求的 ServletRequestAttributes
  • 事务管理:TransactionSynchronizationManager 使用多个 ThreadLocal 变量管理事务资源
  • 安全上下文:Spring Security 的 SecurityContextHolder 默认使用 ThreadLocal 存储认证信息
一个 Spring MVC 应用中使用 ThreadLocal 的示例:
  1. @RestController
  2. public class UserController {
  3.    
  4.     @GetMapping("/current-user")
  5.     public String getCurrentUser() {
  6.         // 从 Spring Security 上下文中获取当前用户
  7.         Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
  8.         if (authentication != null && authentication.isAuthenticated()) {
  9.             return "Current user: " + authentication.getName();
  10.         }
  11.         return "No authenticated user";
  12.     }
  13.    
  14.     @GetMapping("/current-request")
  15.     public String getCurrentRequest() {
  16.         // 从 RequestContextHolder 中获取当前请求
  17.         ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  18.         if (attributes != null) {
  19.             HttpServletRequest request = attributes.getRequest();
  20.             return "Current request URL: " + request.getRequestURL();
  21.         }
  22.         return "No current request";
  23.     }
  24. }
复制代码
7.2 Hibernate 中的 ThreadLocal

Hibernate 使用 ThreadLocal 管理当前会话:
  1. public class HibernateExample {
  2.     private static final SessionFactory sessionFactory; // 假设已经初始化
  3.    
  4.     public void doWithSession() {
  5.         Session session = null;
  6.         Transaction tx = null;
  7.         
  8.         try {
  9.             // 获取当前线程的会话
  10.             session = sessionFactory.getCurrentSession();
  11.             
  12.             // 开始事务
  13.             tx = session.beginTransaction();
  14.             
  15.             // 执行数据库操作
  16.             User user = new User("john", "John Doe");
  17.             session.save(user);
  18.             
  19.             // 提交事务
  20.             tx.commit();
  21.         } catch (Exception e) {
  22.             if (tx != null) {
  23.                 tx.rollback();
  24.             }
  25.             throw e;
  26.         }
  27.         // 注意:不需要关闭 session,因为 getCurrentSession() 会自动管理
  28.     }
  29. }
复制代码
7.3 Log4j/Logback 中的 MDC

日记框架中的 MDC (Mapped Diagnostic Context) 使用 ThreadLocal 来存储日记上下文信息:
  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. import org.slf4j.MDC;
  4. public class LoggingExample {
  5.     private static final Logger logger = LoggerFactory.getLogger(LoggingExample.class);
  6.    
  7.     public void processRequest(String requestId, String userId) {
  8.         // 将请求 ID 和用户 ID 添加到 MDC
  9.         MDC.put("requestId", requestId);
  10.         MDC.put("userId", userId);
  11.         
  12.         try {
  13.             // 日志日志会自动包含 MDC 中的信息
  14.             logger.info("开始处理请求");
  15.             
  16.             // 业务处理...
  17.             businessLogic();
  18.             
  19.             logger.info("请求处理完成");
  20.         } finally {
  21.             // 清理 MDC
  22.             MDC.clear();
  23.         }
  24.     }
  25.    
  26.     private void businessLogic() {
  27.         // 这里的日志日志也会包含 MDC 信息
  28.         logger.info("执行业务逻辑");
  29.     }
  30. }
复制代码
设置日记格式:
  1. <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
  2.     <encoder>
  3.         <pattern>%d{HH:mm:ss.SSS} [%thread] [requestId=%X{requestId}] [userId=%X{userId}] %-5level %logger{36} - %msg%n</pattern>
  4.     </encoder>
  5. </appender>
复制代码
输出示例:
  1. 10:15:23.456 [main] [requestId=abc123] [userId=user456] INFO  LoggingExample - 开始处理请求
  2. 10:15:23.500 [main] [requestId=abc123] [userId=user456] INFO  LoggingExample - 执行业务逻辑
  3. 10:15:23.550 [main] [requestId=abc123] [userId=user456] INFO  LoggingExample - 请求处理完成
复制代码
8. ThreadLocal 的高级主题

8.1 ThreadLocal 与线程池的联合使用

在使用线程池时,需要特别注意整理 ThreadLocal 变量,否则可能导致意外举动或内存泄漏。
一种常见的做法是使用装饰模式包装 Runnable 或 Callable,确保在任务执行前后正确设置和整理 ThreadLocal 变量:
  1. public class ThreadLocalAwareRunnable implements Runnable {
  2.     private final Runnable delegate;
  3.     private final Map<ThreadLocal<?>, Object> threadLocalValues;
  4.    
  5.     public ThreadLocalAwareRunnable(Runnable delegate, Map<ThreadLocal<?>, Object> threadLocalValues) {
  6.         this.delegate = delegate;
  7.         this.threadLocalValues = new HashMap<>(threadLocalValues);
  8.     }
  9.    
  10.     @Override
  11.     public void run() {
  12.         // 保存当前线程的 ThreadLocal 值
  13.         Map<ThreadLocal<?>, Object> previousValues = new HashMap<>();
  14.         for (Map.Entry<ThreadLocal<?>, Object> entry : threadLocalValues.entrySet()) {
  15.             ThreadLocal<Object> threadLocal = (ThreadLocal<Object>) entry.getKey();
  16.             previousValues.put(threadLocal, threadLocal.get());
  17.             threadLocal.set(entry.getValue());
  18.         }
  19.         
  20.         try {
  21.             // 执行原始任务
  22.             delegate.run();
  23.         } finally {
  24.             // 恢复原来的 ThreadLocal 值
  25.             for (Map.Entry<ThreadLocal<?>, Object> entry : threadLocalValues.entrySet()) {
  26.                 ThreadLocal<Object> threadLocal = (ThreadLocal<Object>) entry.getKey();
  27.                 Object previousValue = previousValues.get(threadLocal);
  28.                 if (previousValue != null) {
  29.                     threadLocal.set(previousValue);
  30.                 } else {
  31.                     threadLocal.remove();
  32.                 }
  33.             }
  34.         }
  35.     }
  36. }
  37. // 使用示例
  38. public class ThreadPoolThreadLocalExample {
  39.     private static final ThreadLocal<String> userThreadLocal = new ThreadLocal<>();
  40.    
  41.     public static void main(String[] args) {
  42.         ExecutorService executor = Executors.newFixedThreadPool(5);
  43.         
  44.         // 设置主线程的 ThreadLocal 值
  45.         userThreadLocal.set("MainUser");
  46.         
  47.         // 创建 ThreadLocal 值映射
  48.         Map<ThreadLocal<?>, Object> threadLocalValues = new HashMap<>();
  49.         threadLocalValues.put(userThreadLocal, "TaskUser");
  50.         
  51.         // 提交装饰后的任务
  52.         executor.execute(new ThreadLocalAwareRunnable(() -> {
  53.             System.out.println("User in task: " + userThreadLocal.get());
  54.         }, threadLocalValues));
  55.         
  56.         executor.shutdown();
  57.     }
  58. }
复制代码
8.2 ThreadLocal 的性能考虑

ThreadLocal 虽然可以避免同步开销,但它也有自己的性能特点:

  • 空间开销:每个线程都有自己的副本,如果存储大对象且线程数目多,会消耗更多内存。
  • 哈希查找开销:ThreadLocalMap 是基于开放地点法的哈希表,查找也需要一定的时间。
  • 初始化开销:如果使用 initialValue 方法或 withInitial 方法,每个线程初次访问时都会执行初始化逻辑。
性能优化发起:

  • 减少 ThreadLocal 的数目:合并相关的变量到一个上下文对象中,减少 ThreadLocal 实例的数目。
  • 避免存储大对象:如果需要存储大对象,考虑只存储引用或标识符。
  • 懒加载大对象:针对大对象,使用懒加载方式:
    1. private static final ThreadLocal<ExpensiveObject> expensiveObjectThreadLocal =
    2.     ThreadLocal.withInitial(() -> null);
    3. public static ExpensiveObject getExpensiveObject() {
    4.     ExpensiveObject object = expensiveObjectThreadLocal.get();
    5.     if (object == null) {
    6.         object = createExpensiveObject();
    7.         expensiveObjectThreadLocal.set(object);
    8.     }
    9.     return object;
    10. }
    复制代码
8.3 使用 ThreadLocal.ThreadLocalMap 的高级用法

ThreadLocal.ThreadLocalMap 是 ThreadLocal 内部使用的数据布局,通常不直接使用。但是,在某些高级场景下,可能需要自界说类似的机制:
  1. public class CustomThreadLocalMap<K, V> {
  2.     private final ThreadLocal<Map<K, V>> threadLocal = ThreadLocal.withInitial(HashMap::new);
  3.    
  4.     public V get(K key) {
  5.         return threadLocal.get().get(key);
  6.     }
  7.    
  8.     public void put(K key, V value) {
  9.         threadLocal.get().put(key, value);
  10.     }
  11.    
  12.     public void remove(K key) {
  13.         threadLocal.get().remove(key);
  14.     }
  15.    
  16.     public boolean containsKey(K key) {
  17.         return threadLocal.get().containsKey(key);
  18.     }
  19.    
  20.     public void clear() {
  21.         threadLocal.get().clear();
  22.     }
  23.    
  24.     public Set<K> keySet() {
  25.         return threadLocal.get().keySet();
  26.     }
  27.    
  28.     public Collection<V> values() {
  29.         return threadLocal.get().values();
  30.     }
  31.    
  32.     public Set<Map.Entry<K, V>> entrySet() {
  33.         return threadLocal.get().entrySet();
  34.     }
  35.    
  36.     public void removeThreadLocalMap() {
  37.         threadLocal.remove();
  38.     }
  39. }
  40. // 使用示例
  41. public class CustomThreadLocalMapExample {
  42.     private static final CustomThreadLocalMap<String, Object> contextMap = new CustomThreadLocalMap<>();
  43.    
  44.     public static void main(String[] args) {
  45.         // 设置值
  46.         contextMap.put("userId", "user123");
  47.         contextMap.put("requestId", "req456");
  48.         
  49.         // 获取值
  50.         String userId = (String) contextMap.get("userId");
  51.         System.out.println("User ID: " + userId);
  52.         
  53.         // 遍历所有值
  54.         for (Map.Entry<String, Object> entry : contextMap.entrySet()) {
  55.             System.out.println(entry.getKey() + ": " + entry.getValue());
  56.         }
  57.         
  58.         // 清理
  59.         contextMap.clear();
  60.         // 或者完全移除
  61.         contextMap.removeThreadLocalMap();
  62.     }
  63. }
复制代码
9. 总结

ThreadLocal 是 Java 多线程编程中的紧张工具,它为每个线程提供独立的变量副本,解决了特定范例的并发问题。本文具体先容了 ThreadLocal 的使用方法、工作原理、应用场景以及潜在的内存泄漏问题及其解决方案。
9.1 主要要点回顾



  • ThreadLocal 的核心功能:为每个线程提供变量的独立副本
  • 常用方法:set()、get()、remove()、withInitial()
  • 常见应用场景:用户上下文、数据库毗连管理、事务管理等
  • 内存泄漏:由于 ThreadLocalMap 的 Entry 是弱引用,未整理的值可能导致内存泄漏
  • 最佳实践:总是在 finally 块中调用 remove(),使用静态 final 修饰符,优先使用初始化器
9.2 何时选择 ThreadLocal



  • 当你需要在同一个线程的多个方法之间转达变量
  • 当你需要避免在参数中转达上下文对象
  • 当你需要线程安全但又不想使用同步机制
  • 当变量的生命周期与线程的生命周期相似
9.3 何时避免使用 ThreadLocal



  • 当变量需要在线程之间共享
  • 在高频创建和销毁线程的场景
  • 存储生命周期与线程差别等的对象
正确使用 ThreadLocal 可以简化代码,提高性能,但也需要注意潜在的风险。希望本文能资助你在 Java 多线程编程中更好地使用 ThreadLocal。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表