当前位置: 首页 > news >正文

ThreadLocal详解与实战指南

目录

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 {@Overridepublic 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);// 存储到ThreadLocalCONN_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(() -> {// 使用同一个连接执行多个SQLConnection 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();// 获取生成的用户IDtry (ResultSet rs = ps.getGeneratedKeys()) {if (rs.next()) {int userId = rs.getInt(1);// 插入地址,关联用户IDtry (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();// 如果不存在,则生成新的追踪IDif (traceId == null) {traceId = generateTraceId();TRACE_ID_HOLDER.set(traceId);}return traceId;}public static void clear() {TRACE_ID_HOLDER.remove();}// 生成唯一的追踪IDprivate static String generateTraceId() {return UUID.randomUUID().toString().replace("-", "");}
        }
        
         2.创建请求拦截器
        @Component
        public class TraceInterceptor implements HandlerInterceptor {private static final Logger logger = LoggerFactory.getLogger(TraceInterceptor.class);@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 尝试从请求头中获取追踪IDString 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;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {// 请求完成后清理TraceContext.clear();}
        }
        
        3.配置拦截器 
          @Configuration
          public class WebConfig implements WebMvcConfigurer {@Autowiredprivate TraceInterceptor traceInterceptor;@Overridepublic 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 {@Overridepublic 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 {@Beanpublic RestTemplate restTemplate(TraceRestTemplateInterceptor traceInterceptor) {RestTemplate restTemplate = new RestTemplate();// 添加追踪拦截器restTemplate.setInterceptors(Collections.singletonList(traceInterceptor));return restTemplate;}
          }
          
          7. 在业务代码中使用
          @Service
          public class UserService {@Autowiredprivate 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 {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 从 Header 中提取信息String authToken = request.getHeader("Authorization");String traceId = request.getHeader("X-Trace-Id");// 存储到 ThreadLocalif (authToken != null) {RequestContext.setAuthToken(authToken);}if (traceId != null) {RequestContext.setTraceId(traceId);MDC.put("traceId", traceId); 这里加上值}return true; // 继续执行后续拦截器和控制器}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {// 请求完成后清理 ThreadLocal,避免内存泄漏RequestContext.clear();}
          }
          3.配置拦截器
          @Configuration
          public class WebConfig implements WebMvcConfigurer {@Autowiredprivate HeaderInterceptor traceInterceptor;@Overridepublic 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();}// 完全清除ThreadLocalpublic 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>>() {@Overrideprotected 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 {@Beanpublic Executor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setQueueCapacity(25);executor.setThreadNamePrefix("AsyncTask-");// 创建自定义的TaskDecorator,用于传递ThreadLocalexecutor.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 {@Autowiredprivate 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();}@Overridepublic 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 {@Beanpublic Executor asyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(10);executor.setMaxPoolSize(20);executor.setQueueCapacity(100);executor.setThreadNamePrefix("Async-");// 包装Executor,使其支持TransmittableThreadLocalexecutor.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>() {@Overrideprotected 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是为了隔离线程数据,而非共享数据

          相关文章:

        1. 深入浅出Sentinel:分布式系统的流量防卫兵
        2. 【uniapp】vue2 搜索文字高亮显示
        3. MongoDB Shard Cluster
        4. 科技赋能建筑新未来:中建海龙模块化建筑产品入选中国建筑首批产业化推广产品
        5. Kafka和flume整合
        6. HOW - 如何模拟实现 gpt 展示答案的交互效果
        7. Python判断语句-语法:if,if else,if elif else,嵌套,if else语句扁平式写法,案例
        8. android jatpack Compose 多数据源依赖处理:从状态管理到精准更新的架构设计
        9. kafka整合flume与DStream转换
        10. #苍穹外卖# day 10-11
        11. Move Registry 发布,实现 Sui 的超级互操作性
        12. ubuntu22.04部署Snipe-IT
        13. MYSQL 常用字符串函数 和 时间函数详解
        14. 信息学奥赛一本通 1509:【例 1】Intervals | OpenJudge 百练 1201:Intervals
        15. 云服务器centos 安装hadoop集群
        16. CS001-7-hbao
        17. 海之淀攻略
        18. 【视频时刻检索】Text-Video Retrieval via Multi-Modal Hypergraph Networks 论文阅读
        19. 驱动开发硬核特训 · Day 21(上篇) 抽象理解 Linux 子系统:内核工程师的视角
        20. Spring的xxxAware接口工作原理-笔记
        21. 全国首例!上市公司董监高未履行公开增持承诺,投资者起诉获赔
        22. 中央空管办组织加强无人机“黑飞”“扰航”查处力度
        23. 魔都眼·上海车展④|奔驰宝马保时捷……全球豪车扎堆首秀
        24. 著名哲学家、中山大学哲学系原系主任李锦全逝世
        25. 观察|动力电池步入“多核时代”,宁德时代新技术密集开箱有何启示
        26. 电商平台全面取消“仅退款”:电商反内卷一大步,行业回归良性竞争