ThreadLocal详解与实战指南

news2025/4/26 5:25:22

目录

1. ThreadLocal基本概念

1.1 核心原理

1.2 主要特性

2. ThreadLocal API详解

2.1 核心方法

2.2 基本使用方式

3. ThreadLocal使用场景与实战

3.1 场景一:用户身份信息传递

实现步骤

1.创建用户上下文类

2.创建过滤器或拦截器来设置和清理用户信息 

3.业务逻辑中使用 

 4.总结

3.2 场景二:事务管理

实现步骤

1.创建数据库连接管理类

 2.创建事务管理工具类

 3.在业务代码中使用

4.总结

3.3 场景三:简化日期格式化

实现步骤

1.创建日期工具类

2. 在多线程环境中使用

3.4 场景四:追踪请求链路

实现步骤

1.创建追踪上下文类

 2.创建请求拦截器

3.配置拦截器 

 4.创建日志工具类(可自定义)

 5.创建RestTemplate拦截器传递追踪ID

 6.配置RestTemplate

7. 在业务代码中使用

3.5 场景五:slf4j——MDC全局日志打印

实现步骤

1.配置lombok(日志约束xml文件)

2.创建拦截器

3.配置拦截器

4.呈现效果

3.6 场景六:线程安全的数据缓存

实现步骤

 1.创建本地缓存类

 2.使用例子

4. ThreadLocal实现继承性 - InheritableThreadLocal

4.1 基本用法

4.2 用于跨线程上下文传递

实现步骤

 1.创建上下文类

2.创建异步执行器 

3.在多线程环境中使用 

4.Controller引入 

5. ThreadLocal常见问题与最佳实践

5.1 内存泄漏问题

最佳实践

1.总是调用remove方法

2. 使用try-with-resources模式

5.2 父子线程值传递问题

使用TransmittableThreadLocal

 1.依赖配置

2.基本实践 

3.Spring集成 

5.3 ThreadLocal的值修改

最佳实践

5.4 初始化ThreadLocal值

6. 总结


1. ThreadLocal基本概念

ThreadLocal是Java提供的一个线程本地变量工具,它允许我们创建只能被同一个线程读写的变量。从线程的角度看,就好像是线程在访问自己的私有变量一样,避免了共享变量可能带来的并发问题。

在这里强调一点,ThreadLocal是绝对不会应用于线程同步的场景的!

ThreadLocal本质:隔离线程数据!而非共享数据!

这一点本文会有意无意多次强调!所以大家在学习TheadLocal的时候,要摒弃掉JUC那一套理论!大伙儿只要清楚,ThreadLocal就是多线程对于共享变量的独立备份,至于这个共享变量是否同时只能被一个线程修改,这一点是无关紧要的。

因为每个线程栈的上下文都是独立、私有的,所以Threadlocal修饰的变量存储的值相对来说肯定也是独立、私有的。

1.1 核心原理

ThreadLocal的工作原理可以简单概括为:

  • 每个Thread对象内部维护了一个ThreadLocalMap
  • ThreadLocalMap是一个定制化的哈希表,其中key是ThreadLocal对象的弱引用,value是该线程存储的变量副本
  • 当线程访问ThreadLocal变量时,实际是在访问自己的ThreadLocalMap中对应的条目

1.2 主要特性

  • 线程隔离性:每个线程拥有各自独立的变量副本
  • 减少同步:避免多线程环境下的同步操作
  • 方便传递上下文:无需通过参数传递上下文信息
  • 资源清理:需要手动移除不再使用的ThreadLocal变量

2. ThreadLocal API详解

2.1 核心方法

方法描述
T get()获取当前线程的ThreadLocal变量副本
void set(T value)设置当前线程的ThreadLocal变量副本
void remove()移除当前线程的ThreadLocal变量副本
protected T initialValue()返回ThreadLocal变量的初始值(默认返回null)
static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)Java 8引入,创建带初始值的ThreadLocal

这里列出了5个,但是只要记住前三个就行:获取值、设置值、移除值(防止内存泄漏

2.2 基本使用方式

// 创建ThreadLocal变量
private ThreadLocal<String> threadLocal = new ThreadLocal<>();

// 设置值
threadLocal.set("线程特定的值");

// 获取值
String value = threadLocal.get();

// 使用完后移除值
threadLocal.remove();

3. ThreadLocal使用场景与实战

3.1 场景一:用户身份信息传递

在Web应用中,用户身份信息需要在同一个请求的不同组件、不同层之间传递,使用ThreadLocal可以避免在多个方法间传递用户对象。

实现步骤
1.创建用户上下文类
public class UserContext {
    private static final ThreadLocal<User> USER_THREAD_LOCAL = new ThreadLocal<>();
    
    public static void setUser(User user) {
        USER_THREAD_LOCAL.set(user);
    }
    
    public static User getUser() {
        return USER_THREAD_LOCAL.get();
    }
    
    public static void clear() {
        USER_THREAD_LOCAL.remove();
    }
}
2.创建过滤器或拦截器来设置和清理用户信息 
    @Component
    public class UserContextFilter implements Filter {
        
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
                throws IOException, ServletException {
            HttpServletRequest req = (HttpServletRequest) request;
            
            try {
                // 从请求中获取用户信息(例如从JWT token中)
                User user = extractUserFromRequest(req);
                // 设置到ThreadLocal中
                UserContext.setUser(user);
                
                // 继续处理请求
                chain.doFilter(request, response);
            } finally {
                // 请求结束后清理ThreadLocal,避免内存泄漏
                UserContext.clear();
            }
        }
        
        private User extractUserFromRequest(HttpServletRequest request) {
            // 从请求头或Cookie中提取用户信息并创建User对象
            // 当然请求头和Cookie不一定非要带上完整的User信息,只要给一个能唯一锁定User的key就好
            // 这里省略具体实现...
            return new User();
        }
    }
    
    3.业务逻辑中使用 
      @Service
      public class UserService {
          
          public void processUserRequest() {
              // 直接获取当前线程关联的用户信息
              User currentUser = UserContext.getUser();
              
              // 使用用户信息进行业务处理
              if (currentUser != null && currentUser.hasPermission("SOME_ACTION")) {
                  // 执行需要权限的操作
              } else {
                  throw new UnauthorizedException("No permission");
              }
          }
      }
      
       4.总结

      实战中我们可以经常遇到这样的场景:请求实时获取当前发起请求用户个人信息

      其实就可以依据上述步骤来完成。

      不可否认信息来源肯定还是来源于:token、header这类载体。常规处理方式,可能在解析到用户信息后放入缓存,或者实时获取。

      但是这种处理方式是基于线程维度来处理,也就是说,在整个线程的生命周期中,所有上下文能够用到用户信息的地方都只需要经过一次解析后,随时随地从内存的ThreadLocalMap中拿到。

      3.2 场景二:事务管理

      在需要手动管理事务的场景中,可以使用ThreadLocal存储数据库连接。

      实现步骤
      1.创建数据库连接管理类
      public class ConnectionManager {
          private static final ThreadLocal<Connection> CONN_HOLDER = new ThreadLocal<>();
          
          public static Connection getConnection() throws SQLException {
              Connection conn = CONN_HOLDER.get();
              
              if (conn == null) {
                  // 创建新连接
                  conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
                  // 设置为手动提交
                  conn.setAutoCommit(false);
                  // 存储到ThreadLocal
                  CONN_HOLDER.set(conn);
              }
              
              return conn;
          }
          
          public static void beginTransaction() throws SQLException {
              Connection conn = getConnection();
              if (conn.getAutoCommit()) {
                  conn.setAutoCommit(false);
              }
          }
          
          public static void commitTransaction() throws SQLException {
              Connection conn = CONN_HOLDER.get();
              if (conn != null) {
                  conn.commit();
              }
          }
          
          public static void rollbackTransaction() throws SQLException {
              Connection conn = CONN_HOLDER.get();
              if (conn != null) {
                  conn.rollback();
              }
          }
          
          public static void close() {
              Connection conn = CONN_HOLDER.get();
              if (conn != null) {
                  try {
                      conn.close();
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }
                  CONN_HOLDER.remove();
              }
          }
      }
      
       2.创建事务管理工具类
      public class TransactionManager {
          
          public static void executeInTransaction(TransactionCallback callback) {
              try {
                  // 开始事务
                  ConnectionManager.beginTransaction();
                  
                  // 执行业务逻辑
                  callback.execute();
                  
                  // 提交事务
                  ConnectionManager.commitTransaction();
              } catch (Exception e) {
                  // 发生异常时回滚
                  try {
                      ConnectionManager.rollbackTransaction();
                  } catch (SQLException ex) {
                      ex.printStackTrace();
                  }
                  throw new RuntimeException(e);
              } finally {
                  // 关闭连接
                  ConnectionManager.close();
              }
          }
          
          // 回调接口
          public interface TransactionCallback {
              void execute() throws Exception;
          }
      }
      
       3.在业务代码中使用
      public class UserRepository {
          
          public void createUserWithAddress(User user, Address address) {
              TransactionManager.executeInTransaction(() -> {
                  // 使用同一个连接执行多个SQL
                  Connection conn = ConnectionManager.getConnection();
                  
                  // 插入用户
                  try (PreparedStatement ps = conn.prepareStatement(
                          "INSERT INTO users (username, email) VALUES (?, ?)",
                          Statement.RETURN_GENERATED_KEYS)) {
                      ps.setString(1, user.getUsername());
                      ps.setString(2, user.getEmail());
                      ps.executeUpdate();
                      
                      // 获取生成的用户ID
                      try (ResultSet rs = ps.getGeneratedKeys()) {
                          if (rs.next()) {
                              int userId = rs.getInt(1);
                              
                              // 插入地址,关联用户ID
                              try (PreparedStatement addrPs = conn.prepareStatement(
                                      "INSERT INTO addresses (user_id, street, city, country) VALUES (?, ?, ?, ?)")) {
                                  addrPs.setInt(1, userId);
                                  addrPs.setString(2, address.getStreet());
                                  addrPs.setString(3, address.getCity());
                                  addrPs.setString(4, address.getCountry());
                                  addrPs.executeUpdate();
                              }
                          }
                      }
                  }
              });
          }
      }
      
      4.总结

      这种手动处理db链接的方式,其实也是借用了jdbc底层源码的套路,只不过在应用层做了一层拦截,强制让一个事务的sql群走一个链接。避免了手动另开事务进程,保证操作原子性。

      能够有效避免数据库链接被占满的异常情况。

      3.3 场景三:简化日期格式化

      SimpleDateFormat不是线程安全的,因为它的内部状态(如 Calendar 实例)会在多线程环境下被修改,导致数据错乱、异常或错误结果。

      使用ThreadLocal可以为每个线程提供独立的实例。

      实现步骤
      1.创建日期工具类
      public class DateUtil {
          // ThreadLocal存储每个线程的SimpleDateFormat实例
          private static final ThreadLocal<SimpleDateFormat> DATE_FORMATTER = 
              ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
          
          // 日期转字符串
          public static String formatDate(Date date) {
              return DATE_FORMATTER.get().format(date);
          }
          
          // 字符串转日期
          public static Date parseDate(String dateStr) throws ParseException {
              return DATE_FORMATTER.get().parse(dateStr);
          }
          
          // 修改日期格式
          public static void setDateFormat(String pattern) {
              DATE_FORMATTER.set(new SimpleDateFormat(pattern));
          }
      }
      
      2. 在多线程环境中使用
        public class DateFormatExample {
            
            public static void main(String[] args) {
                // 创建线程池
                ExecutorService executor = Executors.newFixedThreadPool(10);
                
                for (int i = 0; i < 20; i++) {
                    final int taskId = i;
                    executor.submit(() -> {
                        try {
                            // 每个线程设置不同的日期格式
                            if (taskId % 2 == 0) {
                                DateUtil.setDateFormat("yyyy-MM-dd");
                            } else {
                                DateUtil.setDateFormat("MM/dd/yyyy HH:mm");
                            }
                            
                            // 使用当前线程的格式化器
                            Date now = new Date();
                            String formattedDate = DateUtil.formatDate(now);
                            System.out.println("Thread " + Thread.currentThread().getId() + 
                                              " formatted date: " + formattedDate);
                            
                            // 解析日期
                            Date parsedDate = DateUtil.parseDate(formattedDate);
                            System.out.println("Thread " + Thread.currentThread().getId() + 
                                              " parsed date: " + parsedDate);
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                    });
                }
                
                executor.shutdown();
            }
        }
        

        3.4 场景四:追踪请求链路

        在微服务架构中,需要追踪一个请求在不同服务间的调用链路,可以使用ThreadLocal来存储和传递追踪ID。

        实现步骤
        1.创建追踪上下文类
        public class TraceContext {
            private static final ThreadLocal<String> TRACE_ID_HOLDER = new ThreadLocal<>();
            
            public static void setTraceId(String traceId) {
                TRACE_ID_HOLDER.set(traceId);
            }
            
            public static String getTraceId() {
                String traceId = TRACE_ID_HOLDER.get();
                // 如果不存在,则生成新的追踪ID
                if (traceId == null) {
                    traceId = generateTraceId();
                    TRACE_ID_HOLDER.set(traceId);
                }
                return traceId;
            }
            
            public static void clear() {
                TRACE_ID_HOLDER.remove();
            }
            
            // 生成唯一的追踪ID
            private static String generateTraceId() {
                return UUID.randomUUID().toString().replace("-", "");
            }
        }
        
         2.创建请求拦截器
        @Component
        public class TraceInterceptor implements HandlerInterceptor {
            private static final Logger logger = LoggerFactory.getLogger(TraceInterceptor.class);
            
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
                // 尝试从请求头中获取追踪ID
                String traceId = request.getHeader("X-Trace-ID");
                
                // 如果没有,则生成新的
                if (traceId == null || traceId.isEmpty()) {
                    traceId = TraceContext.getTraceId();
                } else {
                    TraceContext.setTraceId(traceId);
                }
                
                logger.info("Processing request with trace ID: {}", traceId);
                return true;
            }
            
            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                       Object handler, Exception ex) {
                // 请求完成后清理
                TraceContext.clear();
            }
        }
        
        3.配置拦截器 
          @Configuration
          public class WebConfig implements WebMvcConfigurer {
              
              @Autowired
              private TraceInterceptor traceInterceptor;
              
              @Override
              public void addInterceptors(InterceptorRegistry registry) {
                  registry.addInterceptor(traceInterceptor);
              }
          }
          
           4.创建日志工具类(可自定义)

          这一步根据自己项目实际的需求来,这里只是简单举例,比如封装一个切面统一打印日志。

          public class LogUtil {
              private static final Logger logger = LoggerFactory.getLogger(LogUtil.class);
              
              public static void info(String message) {
                  logger.info("[TraceID: {}] {}", TraceContext.getTraceId(), message);
              }
              
              public static void error(String message, Throwable throwable) {
                  logger.error("[TraceID: {}] {}", TraceContext.getTraceId(), message, throwable);
              }
              
              // 其他日志级别方法...
          }
          
           5.创建RestTemplate拦截器传递追踪ID
          @Component
          public class TraceRestTemplateInterceptor implements ClientHttpRequestInterceptor {
              
              @Override
              public ClientHttpResponse intercept(HttpRequest request, byte[] body, 
                                                 ClientHttpRequestExecution execution) throws IOException {
                  // 获取当前线程的追踪ID,并添加到请求头
                  request.getHeaders().add("X-Trace-ID", TraceContext.getTraceId());
                  return execution.execute(request, body);
              }
          }
          
           6.配置RestTemplate
          @Configuration
          public class RestTemplateConfig {
              
              @Bean
              public RestTemplate restTemplate(TraceRestTemplateInterceptor traceInterceptor) {
                  RestTemplate restTemplate = new RestTemplate();
                  // 添加追踪拦截器
                  restTemplate.setInterceptors(Collections.singletonList(traceInterceptor));
                  return restTemplate;
              }
          }
          
          7. 在业务代码中使用
          @Service
          public class UserService {
              
              @Autowired
              private RestTemplate restTemplate;
              
              public User getUserDetails(String userId) {
                  LogUtil.info("Fetching user details for user: " + userId);
                  
                  // 调用用户服务
                  User user = restTemplate.getForObject("/api/users/{id}", User.class, userId);
                  
                  LogUtil.info("Retrieved user: " + user.getUsername());
                  
                  // 调用订单服务
                  List<Order> orders = restTemplate.getForObject("/api/orders?userId={id}", List.class, userId);
                  
                  LogUtil.info("Retrieved " + orders.size() + " orders for user");
                  
                  // 继续处理...
                  return user;
              }
          }
          

          3.5 场景五:slf4j——MDC全局日志打印

          MDC是slf4j提供的线程链路追踪的一种优雅实现,其实和上面那种方式差不多,只不过这里是作用于运行日志。

          因为slf4j是日志门面框架,无论你的项目是使用logback还是log4j2来作为日志框架,最终的日志形式肯定会基于一个xml文件来约束。

          MDC可以保存日志上下文,在打印日志时,打印出来这些上下文信息以上面那个3.4为基础,我们也可以在xml里配置每次都把traceId这个信息打印出来。

          实现步骤
          1.配置lombok(日志约束xml文件)
          <configuration>
              <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
                  <encoder>
                      <!-- 确保包含 %X{traceId} -->
                      <pattern>[%thread] [traceId=%X{traceId}] %-5level %logger{36} - %msg%n</pattern>
                  </encoder>
              </appender>
              <root level="INFO">
                  <appender-ref ref="STDOUT" />
              </root>
          </configuration>
          2.创建拦截器
          public class HeaderInterceptor implements HandlerInterceptor {
          
              @Override
              public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
                  // 从 Header 中提取信息
                  String authToken = request.getHeader("Authorization");
                  String traceId = request.getHeader("X-Trace-Id");
          
                  // 存储到 ThreadLocal
                  if (authToken != null) {
                      RequestContext.setAuthToken(authToken);
                  }
                  if (traceId != null) {
                      RequestContext.setTraceId(traceId);
                      MDC.put("traceId", traceId); 这里加上值
                  }
                  return true; // 继续执行后续拦截器和控制器
              }
          
              @Override
              public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
                  // 请求完成后清理 ThreadLocal,避免内存泄漏
                  RequestContext.clear();
              }
          }
          3.配置拦截器
          @Configuration
          public class WebConfig implements WebMvcConfigurer {
              
              @Autowired
              private HeaderInterceptor traceInterceptor;
              
              @Override
              public void addInterceptors(InterceptorRegistry registry) {
                  registry.addInterceptor(traceInterceptor);
              }
          }
          
          4.呈现效果
          [http-nio-8081-exec-2] [traceId=123e4567-e89b-12d3-a456-426614174000] INFO  org.example.service.WebService - get token:Basic dXNlcm5hbWU6cGFzc3dvcmQ=,traceId:123e4567-e89b-12d3-a456-426614174000
          

          3.6 场景六:线程安全的数据缓存

          在不同线程需要缓存不同数据的场景下,使用ThreadLocal可以避免同步问题。

          其实这种场景很少,文章开头也说了ThreadLocal一般是不会用于线程同步的场景中,但是不代表ThreadLocal不能做。

          线程同步的场景,一般都是使用异步编排、信号量、JUC工具类来解决的。

          实现步骤
           1.创建本地缓存类
          public class LocalCache<K, V> {
              private final ThreadLocal<Map<K, V>> cache = ThreadLocal.withInitial(HashMap::new);
              
              public V get(K key) {
                  return cache.get().get(key);
              }
              
              public void put(K key, V value) {
                  cache.get().put(key, value);
              }
              
              public void remove(K key) {
                  cache.get().remove(key);
              }
              
              public boolean containsKey(K key) {
                  return cache.get().containsKey(key);
              }
              
              public void clear() {
                  cache.get().clear();
              }
              
              // 完全清除ThreadLocal
              public void removeThreadLocal() {
                  cache.remove();
              }
          }
          
           2.使用例子
          public class ProductService {
              
              // 创建产品缓存
              private final LocalCache<String, Product> productCache = new LocalCache<>();
              
              public Product getProduct(String productId) {
                  // 首先尝试从缓存获取
                  Product product = productCache.get(productId);
                  
                  if (product == null) {
                      // 缓存中不存在,从数据库加载
                      product = loadProductFromDb(productId);
                      // 放入缓存
                      productCache.put(productId, product);
                  }
                  
                  return product;
              }
              
              private Product loadProductFromDb(String productId) {
                  // 从数据库加载产品(示例逻辑)
                  try {
                      // 模拟数据库访问延迟
                      Thread.sleep(100);
                      return new Product(productId, "Product " + productId, 99.99);
                  } catch (InterruptedException e) {
                      Thread.currentThread().interrupt();
                      throw new RuntimeException("Loading product interrupted", e);
                  }
              }
              
              // 在一个批处理任务结束时清理缓存
              public void cleanupCache() {
                  productCache.removeThreadLocal();
              }
          }
          

          4. ThreadLocal实现继承性 - InheritableThreadLocal

          每个线程栈都是独立私有的,普通的ThreadLocal无法将变量值从父线程传递到子线程,为解决这个问题,Java提供了InheritableThreadLocal。

          4.1 基本用法

          public class InheritableThreadLocalExample {
              
              // 创建InheritableThreadLocal变量
              private static final InheritableThreadLocal<String> CONTEXT = new InheritableThreadLocal<>();
              
              public static void main(String[] args) {
                  // 在主线程中设置值
                  CONTEXT.set("Main thread value");
                  System.out.println("Main thread: " + CONTEXT.get());
                  
                  // 创建子线程
                  Thread childThread = new Thread(() -> {
                      // 子线程可以继承父线程的值
                      System.out.println("Child thread: " + CONTEXT.get());
                      
                      // 子线程修改值不会影响父线程
                      CONTEXT.set("Child thread value");
                      System.out.println("Child thread after update: " + CONTEXT.get());
                  });
                  
                  childThread.start();
                  
                  try {
                      childThread.join();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  
                  // 主线程的值不受子线程修改的影响
                  System.out.println("Main thread after child execution: " + CONTEXT.get());
              }
          }
          

          4.2 用于跨线程上下文传递

          在复杂的异步调用场景中,使用InheritableThreadLocal传递上下文信息。

          实现步骤
           1.创建上下文类
          public class ApplicationContext {
              
              private static final InheritableThreadLocal<Map<String, Object>> CONTEXT = 
                  new InheritableThreadLocal<Map<String, Object>>() {
                      @Override
                      protected Map<String, Object> initialValue() {
                          return new HashMap<>();
                      }
                  };
              
              public static void set(String key, Object value) {
                  CONTEXT.get().put(key, value);
              }
              
              @SuppressWarnings("unchecked")
              public static <T> T get(String key) {
                  return (T) CONTEXT.get().get(key);
              }
              
              public static void remove(String key) {
                  CONTEXT.get().remove(key);
              }
              
              public static void clear() {
                  CONTEXT.get().clear();
              }
              
              public static void removeThreadLocal() {
                  CONTEXT.remove();
              }
          }
          
          2.创建异步执行器 
          @Configuration
          public class AsyncConfig {
              
              @Bean
              public Executor taskExecutor() {
                  ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
                  executor.setCorePoolSize(5);
                  executor.setMaxPoolSize(10);
                  executor.setQueueCapacity(25);
                  executor.setThreadNamePrefix("AsyncTask-");
                  
                  // 创建自定义的TaskDecorator,用于传递ThreadLocal
                  executor.setTaskDecorator(task -> {
                      // 获取当前线程的上下文
                      Map<String, Object> context = new HashMap<>(ApplicationContext.getAll());
                      
                      return () -> {
                          // 在任务执行前,将上下文设置到新线程
                          try {
                              context.forEach(ApplicationContext::set);
                              // 执行原始任务
                              task.run();
                          } finally {
                              // 任务执行完毕,清理上下文
                              ApplicationContext.clear();
                          }
                      };
                  });
                  
                  return executor;
              }
          }
          
          3.在多线程环境中使用 
          @Service
          public class AsyncService {
              
              @Async("taskExecutor")
              public CompletableFuture<String> processAsync(String input) {
                  // 获取从调用线程继承的上下文
                  String userId = ApplicationContext.get("userId");
                  String traceId = ApplicationContext.get("traceId");
                  
                  System.out.println("Processing in async thread - UserId: " + userId + ", TraceId: " + traceId);
                  
                  // 处理业务逻辑
                  try {
                      Thread.sleep(1000); // 模拟耗时操作
                      return CompletableFuture.completedFuture("Processed: " + input);
                  } catch (InterruptedException e) {
                      Thread.currentThread().interrupt();
                      return CompletableFuture.failedFuture(e);
                  }
              }
          }
          
          4.Controller引入 
          @RestController
          @RequestMapping("/api")
          public class AsyncController {
              
              @Autowired
              private AsyncService asyncService;
              
              @GetMapping("/process")
              public CompletableFuture<String> process(@RequestParam String input, 
                                                      @RequestHeader("X-User-Id") String userId) {
                  // 设置上下文信息
                  ApplicationContext.set("userId", userId);
                  ApplicationContext.set("traceId", UUID.randomUUID().toString());
                  
                  try {
                      // 调用异步服务
                      return asyncService.processAsync(input);
                  } finally {
                      // 清理主线程的上下文
                      ApplicationContext.clear();
                  }
              }
          }
          

          5. ThreadLocal常见问题与最佳实践

          5.1 内存泄漏问题

          ThreadLocal使用不当可能导致内存泄漏,因为ThreadLocalMap的Key是ThreadLocal的弱引用,而Value是强引用。

          最佳实践
          1.总是调用remove方法

          在使用完ThreadLocal后调用remove()方法 

          try {
              threadLocal.set(value);
              // 使用ThreadLocal变量的代码
          } finally {
              threadLocal.remove();
          }
          
          2. 使用try-with-resources模式

          创建AutoCloseable的ThreadLocal包装类

          public class AutoCloseableThreadLocal<T> implements AutoCloseable {
              private final ThreadLocal<T> threadLocal = new ThreadLocal<>();
              
              public void set(T value) {
                  threadLocal.set(value);
              }
              
              public T get() {
                  return threadLocal.get();
              }
              
              @Override
              public void close() {
                  threadLocal.remove();
              }
          }
          
          // 使用方式
          try (AutoCloseableThreadLocal<String> local = new AutoCloseableThreadLocal<>()) {
              local.set("value");
              // 使用local变量
          } // 自动调用close方法,清理ThreadLocal
          

          5.2 父子线程值传递问题

          如前所述,普通ThreadLocal无法将值从父线程传递到子线程。使用InheritableThreadLocal可解决这个问题,但它也有局限性:子线程只在创建时继承父线程的值,后续父线程的修改不会影响子线程。

          对于线程池场景,可以使用阿里巴巴开源的TransmittableThreadLocal库。

          使用TransmittableThreadLocal
           1.依赖配置
          <dependency>
              <groupId>com.alibaba</groupId>
              <artifactId>transmittable-thread-local</artifactId>
              <version>2.14.2</version>
          </dependency>
          
          2.基本实践 
          import com.alibaba.ttl.TransmittableThreadLocal;
          import com.alibaba.ttl.TtlRunnable;
          
          public class TtlExample {
              
              private static final TransmittableThreadLocal<String> CONTEXT = new TransmittableThreadLocal<>();
              
              public static void main(String[] args) {
                  // 创建线程池
                  ExecutorService executor = Executors.newFixedThreadPool(2);
                  
                  // 设置主线程的值
                  CONTEXT.set("Initial Value");
                  
                  // 提交任务
                  executor.submit(TtlRunnable.get(() -> {
                      System.out.println("Task 1: " + CONTEXT.get()); // 输出: Initial Value
                  }));
                  
                  // 修改值
                  CONTEXT.set("Updated Value");
                  
                  // 提交另一个任务
                  executor.submit(TtlRunnable.get(() -> {
                      System.out.println("Task 2: " + CONTEXT.get()); // 输出: Updated Value
                  }));
                  
                  executor.shutdown();
              }
          }
          
          3.Spring集成 
          @Configuration
          public class ThreadPoolConfig {
              
              @Bean
              public Executor asyncExecutor() {
                  ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
                  executor.setCorePoolSize(10);
                  executor.setMaxPoolSize(20);
                  executor.setQueueCapacity(100);
                  executor.setThreadNamePrefix("Async-");
                  
                  // 包装Executor,使其支持TransmittableThreadLocal
                  executor.setTaskDecorator(runnable -> TtlRunnable.get(runnable));
                  
                  return executor;
              }
          }
          

          5.3 ThreadLocal的值修改

          ThreadLocal存储的对象如果是可变的,其内部状态可能被意外修改,导致线程间的数据污染。

          最佳实践
          1. 存储不可变对象:尽量存储String、Integer等不可变对象
          2. 使用深拷贝:如果必须存储可变对象,在get和set时进行深拷贝
          public class SafeUserContextHolder {
              private static final ThreadLocal<User> USER_HOLDER = new ThreadLocal<>();
              
              // 存储用户时进行深拷贝
              public static void setUser(User user) {
                  if (user == null) {
                      USER_HOLDER.remove();
                  } else {
                      // 创建用户对象的副本
                      User userCopy = new User();
                      userCopy.setId(user.getId());
                      userCopy.setUsername(user.getUsername());
                      userCopy.setRoles(new ArrayList<>(user.getRoles()));
                      
                      USER_HOLDER.set(userCopy);
                  }
              }
              
              // 获取用户时进行深拷贝
              public static User getUser() {
                  User user = USER_HOLDER.get();
                  if (user == null) {
                      return null;
                  }
                  
                  // 创建用户对象的副本
                  User userCopy = new User();
                  userCopy.setId(user.getId());
                  userCopy.setUsername(user.getUsername());
                  userCopy.setRoles(new ArrayList<>(user.getRoles()));
                  
                  return userCopy;
              }
              
              public static void clear() {
                  USER_HOLDER.remove();
              }
          }
          

          5.4 初始化ThreadLocal值

          可以通过覆盖initialValue()方法或使用withInitial()方法设置初始值。

          // 方法1:覆盖initialValue方法
          private static final ThreadLocal<SimpleDateFormat> dateFormat = 
              new ThreadLocal<SimpleDateFormat>() {
                  @Override
                  protected SimpleDateFormat initialValue() {
                      return new SimpleDateFormat("yyyy-MM-dd");
                  }
              };
          
          // 方法2:使用withInitial方法(Java 8+)
          private static final ThreadLocal<SimpleDateFormat> dateFormat = 
              ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
          

          6. 总结

          ThreadLocal是一个强大的工具,适用于需要在同一线程内共享数据但又不希望进行线程同步的场景。它的主要应用场景包括:

          1. 用户身份信息传递:在Web应用中传递用户上下文
          2. 事务管理:存储和传递数据库连接
          3. 线程安全的对象:为每个线程提供专用的非线程安全对象
          4. 请求链路追踪:在分布式系统中追踪请求

          注意事项:

          • 及时清理:使用完毕后调用remove()方法,避免内存泄漏
          • 合理封装:将ThreadLocal的操作封装在统一的上下文管理类中
          • 注意线程复用:在线程池环境中要特别小心ThreadLocal的使用
          • 数据隔离清楚认识ThreadLocal是为了隔离线程数据,而非共享数据

          本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2343012.html

          如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

          相关文章

          【含文档+PPT+源码】基于微信小程序的校园快递平台

          项目介绍 本课程演示的是一款基于微信小程序的校园快递平台&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系统 3.该项目附带…

          【CODEMATE】进制转换(transform) 粤港澳青少年信息学创新大赛 C/C++/Python 解题思路

          目录 问题描述做题思路&#xff0c;解决过程思路&#xff1a;踩过的坑&#xff1a;核心代码C 语言 / C 切片&#xff1a;C 语言 / C 判断 ‘A’ 数量&#xff1a;Python 切片&#xff1a;Python 判断 ‘A’ 数量&#xff1a; 完整代码C 语言 完整代码C 完整代码Python 完整代码…

          2025 Java 开发避坑指南:如何避免踩依赖管理的坑?

          在 Java 开发的世界里&#xff0c;依赖管理就像是一座看不见的桥梁&#xff0c;连接着项目所需的各种第三方库和框架。然而&#xff0c;这座桥梁并非总是稳固&#xff0c;稍有不慎就可能掉入 “依赖地狱”&#xff0c;导致项目编译失败、运行异常。2025 年&#xff0c;随着开源…

          ARM服务器与X86服务器核心区别分析

          ARM服务器与X86服务器核心区别分析 一、架构设计与指令集差异 指令集本质‌ ARM‌&#xff1a;基于RISC&#xff08;精简指令集&#xff09;&#xff0c;指令定长且简单&#xff0c;单周期执行效率高&#xff0c;硬件设计复杂度低&#xff0c;适合低功耗场景。 X86‌&#xf…

          人口老龄化丨AI健康小屋如何实现防病于未然​

          随着全球老龄化加剧&#xff0c;“银发浪潮” 对医疗资源、养老护理和健康管理提出了严峻挑战。 由此智绅科技应运而生&#xff0c;七彩喜智慧养老系统构筑居家养老安全网。 AI 健康小屋作为银发科技的创新载体&#xff0c;通过智能化健康监测、精准化风险预警、便捷化医疗衔…

          记录搭建自己应用中心

          记录搭建自己应用中心 应用架构主应用-管理中心系统文件系统子应用 日志系统日志系统前端日志系统后端 用户系统接入使用暂未完成 研发管理需求面板消息推送任务分配应用发布 应用架构 一直想做个试试&#xff0c;这是一个简易版的&#xff0c;主要是整合下知识的&#xff0c;…

          git版本回退 | 远程仓库的回退 (附实战Demo)

          目录 前言1. 基本知识2. Demo3. 彩蛋 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 爬虫神器&#xff0c;无代码爬取&#xff0c;就来&#xff1a;bright.cn 本身暂存区有多个文件&#xff0c;但手快了&…

          STM32 的 GPIO和中断

          GPIO的简单介绍 内部结构 施密特触发器&#xff08;TTL肖特基触发器&#xff09; 的工作原理&#xff1a; 施密特触发电路&#xff08;简称&#xff09;是一种波形整形电路&#xff0c;当任何波形的信号进入电路时&#xff0c;输出在正、负饱和之间跳动&#xff0c;产生方波或…

          【因果推断】(二)CV中的应用

          文章目录 因果表征学习因果图 (Causal Diagram)“后门准则”&#xff08;backdoor criterion&#xff09;和“前门准则”&#xff08;frontdoor criterion&#xff09;后门调整Visual Commonsense R-CNNCausal Intervention for Weakly-Supervised Semantic SegmentationCausal…

          分享Matlab成功安装Support Package硬件支持包的方法

          分享Matlab成功安装Support Package硬件支持包的方法 文章目录 分享Matlab成功安装Support Package硬件支持包的方法一、 引言二、 操作步骤三、 附件资料四、总结 一、 引言 最近&#xff0c;我想学习基于Matlab simscape & Arduino实现硬件在环仿真&#xff0c;其中物理…

          电子级甲基氯硅烷

          电子级甲基氯硅烷是一类高纯度有机硅化合物&#xff0c;主要用于半导体制造、光伏产业及高端电子材料领域。以下从技术特性、应用场景、生产工艺、市场动态及安全规范等方面展开分析&#xff1a; 一、核心特性与技术标准 高纯度要求 电子级甲基氯硅烷的纯度通常需达到99.99% 以…

          【金仓数据库征文】- 深耕国产数据库优化,筑牢用户体验新高度

          目录 引言 一、性能优化&#xff1a;突破数据处理极限&#xff0c;提升运行效率 1.1 智能查询优化器&#xff1a;精准优化数据检索路径 1.2 并行处理技术&#xff1a;充分释放多核计算潜力 1.3 智能缓存机制&#xff1a;加速数据访问速度 二、稳定性提升&#xff1a;筑牢…

          热度大幅度下降,25西电经济与管理学院(考研录取情况)

          1、经济与管理学院各个方向 2、经济与管理学院近三年复试分数线对比 学长、学姐分析 由表可看出&#xff1a; 1、应用经济及学25年相较于24年下降25分&#xff0c;为325分 2、管理科学与工程25年相较于24年保持不变&#xff0c;为375分 3、工商管理学25年相较于24年下降5分…

          DeepSeek+Mermaid:轻松实现可视化图表自动化生成(附实战演练)

          目录 一、引言&#xff1a;AI 与图表的梦幻联动二、DeepSeek&#xff1a;大语言模型新星崛起2.1 DeepSeek 全面剖析2.2 多场景应用示例2.2.1 文本生成2.2.2 代码编写 三、Mermaid&#xff1a;代码式图表绘制专家3.1 Mermaid 基础探秘3.2 语法与图表类型详解3.2.1 流程图&#x…

          今日行情明日机会——20250425

          指数依然在震荡&#xff0c;等待方向选择&#xff0c;整体量能不搞但个股红多绿少。 2025年4月25日涨停板行业方向分析如下&#xff1a; 一、核心行业方向及驱动逻辑 一季报增长&#xff08;17家涨停&#xff09; 核心个股&#xff1a;惠而浦、鸿博股份、卫星化学驱动逻辑&am…

          一道MySQL索引题

          复合索引基础 MySQL中的复合索引(Composite Index)是指由多个列组成的索引。与单列索引不同、复合索引的结构更为复杂&#xff0c;但使用得当可以大幅提升查询性能。 复合索引的工作原理 复合索引的本质是一种有序的数据结、每个列是建立在那个索引前一列存在的情况下、那一…

          【linux】设置邮件发送告警功能

          当服务器内存不足或者其他故障时&#xff0c;可以通过自动发送故障到邮箱进行提醒。 步骤&#xff1a; 以qq邮箱为例&#xff1a; 登录qq邮箱点击设置 点击账号后&#xff0c;往下翻 找到POP3/IMAP...开启服务 复制授权码 安装邮箱功能 编辑/etc/s-nail.rc 验证 …

          【手机】vivo手机应用声音分离方案

          文章目录 前言方案 前言 尝试分离vivo手机音乐与其他应用的声音 方案 最佳方案&#xff1a;网易云音乐设置内关闭音量均衡 上传不同的白噪音&#xff0c;成功 goodlock&#xff0c;主要适用于三星手机&#xff0c;vivo不一定适用 app volume control &#xff0c;可行

          关于Safari浏览器在ios<16.3版本不支持正则表达式零宽断言的解决办法

          异常原因 今天在升级Dify版本的时候发现低版本的ios手机出现了以下报错&#xff1a; SyntaxError: Invalid regular expression: invalid group specifier nameError: Invalid regular expression: invalid group specifier name Call Stack 46 eval [native code] (0:0) ./n…

          管理+技术”双轮驱动工业企业能源绿色转型

          00序言 在“3060双碳”政策目标下&#xff0c;工业领域作为碳排放的主要来源&#xff08;占比约70%&#xff09;&#xff0c;国家出台《工业领域碳达峰实施方案》《加快推动制造业绿色化发展的指导意见》等文件&#xff0c;明确行业碳达峰时间表和重点任务&#xff0c;完善碳市…