再见了ThreadLocal,我决定用ScopedValue!

前言

今天我们来聊聊一个即将改变我们编程习惯的新特性——ScopedValue。

有些小伙伴在工作中,一提到线程内数据传递就想到ThreadLocal,但真正用起来却遇到各种坑:内存泄漏、数据污染、性能问题等等。

其实,ScopedValue就像ThreadLocal的升级版,既保留了优点,又解决了痛点。

我们一起聊聊ScopedValue的优势和用法,希望对你会有所帮助。

一、ThreadLocal的痛点

在介绍ScopedValue之前,我们先回顾一下ThreadLocal的常见问题。

有些小伙伴可能会想:"ThreadLocal用得好好的,为什么要换?"

其实,ThreadLocal在设计上存在一些固有缺陷。

ThreadLocal的内存泄漏问题

为了更直观地理解ThreadLocal的内存泄漏问题,我画了一个内存泄漏的示意图:

再见了ThreadLocal,我决定用ScopedValue!

ThreadLocal的典型问题代码

/**  * ThreadLocal典型问题演示  */ public class ThreadLocalProblems {          private static final ThreadLocal<UserContext> userContext = new ThreadLocal<>();     private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();          /**      * 问题1:内存泄漏 - 忘记调用remove()      */     public void processRequest(HttpServletRequest request) {         // 设置用户上下文         UserContext context = new UserContext(request.getHeader("X-User-Id"));         userContext.set(context);                  try {             // 业务处理             businessService.process();                          // 问题:忘记调用 userContext.remove()             // 在线程池中,这个线程被重用时,还会保留之前的用户信息         } catch (Exception e) {             // 异常处理         }     }          /**      * 问题2:数据污染 - 线程复用导致数据混乱      */     public void processMultipleRequests() {         // 线程池处理多个请求         ExecutorService executor = Executors.newFixedThreadPool(5);                  for (int i = 0; i < 10; i++) {             final int userId = i;             executor.submit(() -> {                 // 设置用户上下文                 userContext.set(new UserContext("user_" + userId));                                  try {                     // 模拟业务处理                     Thread.sleep(100);                                          // 问题:如果线程被复用,这里可能读取到错误的用户信息                     String currentUser = userContext.get().getUserId();                     System.out.println("处理用户: " + currentUser);                                      } catch (InterruptedException e) {                     Thread.currentThread().interrupt();                 } finally {                     // 即使调用remove,也可能因为异常跳过                     userContext.remove(); // 不保证一定执行                 }             });         }                  executor.shutdown();     }          /**      * 问题3:继承性问题 - 子线程无法继承父线程数据      */     public void parentChildThreadProblem() {         userContext.set(new UserContext("parent_user"));                  Thread childThread = new Thread(() -> {             // 这里获取不到父线程的ThreadLocal值             UserContext context = userContext.get(); // null             System.out.println("子线程用户: " + context); // 输出null                          // 需要手动传递数据         });                  childThread.start();     }          /**      * 问题4:性能问题 - 大量ThreadLocal影响性能      */     public void performanceProblem() {         long startTime = System.currentTimeMillis();                  for (int i = 0; i < 100000; i++) {             ThreadLocal<String> tl = new ThreadLocal<>();             tl.set("value_" + i);             String value = tl.get();             tl.remove();         }                  long endTime = System.currentTimeMillis();         System.out.println("ThreadLocal操作耗时: " + (endTime - startTime) + "ms");     } }  /**  * 用户上下文  */ class UserContext {     private final String userId;     private final long timestamp;          public UserContext(String userId) {         this.userId = userId;         this.timestamp = System.currentTimeMillis();     }          public String getUserId() {         return userId;     }          public long getTimestamp() {         return timestamp;     }          @Override     public String toString() {         return "UserContext{userId='" + userId + "', timestamp=" + timestamp + "}";     } } 

ThreadLocal问题的根本原因

  1. 生命周期管理复杂:需要手动调用set/remove,容易遗漏
  2. 内存泄漏风险:线程池中线程复用,Value无法被GC
  3. 继承性差:子线程无法自动继承父线程数据
  4. 性能开销:ThreadLocalMap的哈希表操作有开销

有些小伙伴可能会问:"我们用InheritableThreadLocal不就能解决继承问题了吗?"

我的经验是:InheritableThreadLocal只是缓解了问题,但带来了新的复杂度,而且性能更差

二、ScopedValue:新一代线程局部变量

ScopedValue是Java 20中引入的预览特性,在Java 21中成为正式特性。

它旨在解决ThreadLocal的痛点,提供更安全、更高效的线程内数据传递方案。

ScopedValue的核心设计理念

为了更直观地理解ScopedValue的工作原理,我画了一个ScopedValue的架构图:

再见了ThreadLocal,我决定用ScopedValue!

ScopedValue的核心优势:

再见了ThreadLocal,我决定用ScopedValue!

ScopedValue基础用法

/**  * ScopedValue基础用法演示  */ public class ScopedValueBasics {          // 1. 定义ScopedValue(相当于ThreadLocal)     private static final ScopedValue<UserContext> USER_CONTEXT = ScopedValue.newInstance();     private static final ScopedValue<Connection> DB_CONNECTION = ScopedValue.newInstance();     private static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();          /**      * 基础用法:在作用域内使用ScopedValue      */     public void basicUsage() {         UserContext user = new UserContext("user_123");                  // 在作用域内绑定值         ScopedValue.runWhere(USER_CONTEXT, user, () -> {             // 在这个作用域内,USER_CONTEXT.get()返回user_123             System.out.println("当前用户: " + USER_CONTEXT.get().getUserId());                          // 可以嵌套使用             ScopedValue.runWhere(REQUEST_ID, "req_456", () -> {                 System.out.println("请求ID: " + REQUEST_ID.get());                 System.out.println("用户: " + USER_CONTEXT.get().getUserId());             });                          // 这里REQUEST_ID已经超出作用域,获取会抛出异常         });                  // 这里USER_CONTEXT已经超出作用域     }          /**      * 带返回值的作用域      */     public String scopedValueWithReturn() {         UserContext user = new UserContext("user_789");                  // 使用callWhere获取返回值         String result = ScopedValue.callWhere(USER_CONTEXT, user, () -> {             // 业务处理             String userId = USER_CONTEXT.get().getUserId();             return "处理用户: " + userId;         });                  return result;     }          /**      * 多个ScopedValue同时使用      */     public void multipleScopedValues() {         UserContext user = new UserContext("user_multi");         Connection conn = createConnection();                  // 同时绑定多个ScopedValue         ScopedValue.runWhere(             ScopedValue.where(USER_CONTEXT, user)                       .where(DB_CONNECTION, conn)                       .where(REQUEST_ID, "multi_req"),             () -> {                 // 在这个作用域内可以访问所有绑定的值                 processBusinessLogic();             }         );                  // 作用域结束后自动清理     }          /**      * 异常处理示例      */     public void exceptionHandling() {         UserContext user = new UserContext("user_exception");                  try {             ScopedValue.runWhere(USER_CONTEXT, user, () -> {                 // 业务处理                 processBusinessLogic();                                  // 如果抛出异常,作用域也会正常结束                 if (someCondition()) {                     throw new RuntimeException("业务异常");                 }             });         } catch (RuntimeException e) {             // 异常处理             System.out.println("捕获异常: " + e.getMessage());         }                  // 即使发生异常,USER_CONTEXT也会自动清理     }          private Connection createConnection() {         // 创建数据库连接         return null;     }          private void processBusinessLogic() {         // 业务逻辑处理         UserContext user = USER_CONTEXT.get();         System.out.println("处理业务逻辑,用户: " + user.getUserId());     }          private boolean someCondition() {         return Math.random() > 0.5;     } } 

三、ScopedValue vs ThreadLocal:全面对比

有些小伙伴可能还想知道ScopedValue到底比ThreadLocal强在哪里。

让我们通过详细的对比来看看。

3.1 内存管理对比

为了更直观地理解两者的内存管理差异,我画了几张图做对比。

ThreadLocal的内存模型图:
再见了ThreadLocal,我决定用ScopedValue!

ScopedValue的内存模型图:
再见了ThreadLocal,我决定用ScopedValue!

二者的关键差异如下图:
再见了ThreadLocal,我决定用ScopedValue!

3.2 代码对比示例

/**  * ThreadLocal vs ScopedValue 对比演示  */ public class ThreadLocalVsScopedValue {          // ThreadLocal方式     private static final ThreadLocal<UserContext> TL_USER_CONTEXT = new ThreadLocal<>();     private static final ThreadLocal<Connection> TL_CONNECTION = new ThreadLocal<>();          // ScopedValue方式     private static final ScopedValue<UserContext> SV_USER_CONTEXT = ScopedValue.newInstance();     private static final ScopedValue<Connection> SV_CONNECTION = ScopedValue.newInstance();          /**      * ThreadLocal方式 - 传统实现      */     public void processRequestThreadLocal(HttpServletRequest request) {         // 设置上下文         UserContext userContext = new UserContext(request.getHeader("X-User-Id"));         TL_USER_CONTEXT.set(userContext);                  Connection conn = null;         try {             // 获取数据库连接             conn = dataSource.getConnection();             TL_CONNECTION.set(conn);                          // 业务处理             processBusinessLogic();                      } catch (SQLException e) {             // 异常处理             handleException(e);         } finally {             // 必须手动清理 - 容易忘记!             TL_USER_CONTEXT.remove();             TL_CONNECTION.remove();                          // 关闭连接             if (conn != null) {                 try {                     conn.close();                 } catch (SQLException e) {                     // 日志记录                 }             }         }     }          /**      * ScopedValue方式 - 现代实现      */     public void processRequestScopedValue(HttpServletRequest request) {         UserContext userContext = new UserContext(request.getHeader("X-User-Id"));                  // 使用try-with-resources管理连接         try (Connection conn = dataSource.getConnection()) {                          // 在作用域内执行,自动管理生命周期             ScopedValue.runWhere(                 ScopedValue.where(SV_USER_CONTEXT, userContext)                           .where(SV_CONNECTION, conn),                 () -> {                     // 业务处理                     processBusinessLogic();                 }             );                          // 作用域结束后自动清理,无需手动remove         } catch (SQLException e) {             handleException(e);         }     }          /**      * 业务逻辑处理 - 两种方式对比      */     private void processBusinessLogic() {         // ThreadLocal方式 - 需要处理null值         UserContext tlUser = TL_USER_CONTEXT.get();         if (tlUser == null) {             throw new IllegalStateException("用户上下文未设置");         }                  Connection tlConn = TL_CONNECTION.get();         if (tlConn == null) {             throw new IllegalStateException("数据库连接未设置");         }                  // ScopedValue方式 - 在作用域内保证不为null         UserContext svUser = SV_USER_CONTEXT.get(); // 不会为null         Connection svConn = SV_CONNECTION.get();    // 不会为null                  // 实际业务处理...         System.out.println("处理用户: " + svUser.getUserId());     }          /**      * 线程池场景对比      */     public void threadPoolComparison() {         ExecutorService executor = Executors.newFixedThreadPool(5);                  // ThreadLocal方式 - 容易出问题         for (int i = 0; i < 10; i++) {             final int userId = i;             executor.submit(() -> {                 TL_USER_CONTEXT.set(new UserContext("user_" + userId));                 try {                     processBusinessLogic();                 } finally {                     TL_USER_CONTEXT.remove(); // 容易忘记或异常跳过                 }             });         }                  // ScopedValue方式 - 更安全         for (int i = 0; i < 10; i++) {             final int userId = i;             executor.submit(() -> {                 UserContext user = new UserContext("user_" + userId);                 ScopedValue.runWhere(SV_USER_CONTEXT, user, () -> {                     processBusinessLogic(); // 自动管理生命周期                 });             });         }                  executor.shutdown();     }          private Connection getConnectionFromTL() {         return TL_CONNECTION.get();     }          private DataSource dataSource = null; // 模拟数据源     private void handleException(SQLException e) {} // 异常处理 } 

3.3 性能对比测试

/**  * 性能对比测试  */ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @State(Scope.Thread) public class PerformanceComparison {          private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();     private static final ScopedValue<String> SCOPED_VALUE = ScopedValue.newInstance();          private static final int ITERATIONS = 100000;          /**      * ThreadLocal性能测试      */     @Benchmark     public void threadLocalPerformance() {         for (int i = 0; i < ITERATIONS; i++) {             THREAD_LOCAL.set("value_" + i);             String value = THREAD_LOCAL.get();             THREAD_LOCAL.remove();         }     }          /**      * ScopedValue性能测试      */     @Benchmark     public void scopedValuePerformance() {         for (int i = 0; i < ITERATIONS; i++) {             ScopedValue.runWhere(SCOPED_VALUE, "value_" + i, () -> {                 String value = SCOPED_VALUE.get();                 // 自动清理,无需remove             });         }     }          /**      * 实际场景性能测试      */     public void realScenarioTest() {         long tlStart = System.nanoTime();                  // ThreadLocal场景         THREAD_LOCAL.set("initial_value");         for (int i = 0; i < ITERATIONS; i++) {             String current = THREAD_LOCAL.get();             THREAD_LOCAL.set(current + "_" + i);         }         THREAD_LOCAL.remove();                  long tlEnd = System.nanoTime();                  // ScopedValue场景         long svStart = System.nanoTime();                  ScopedValue.runWhere(SCOPED_VALUE, "initial_value", () -> {             String current = SCOPED_VALUE.get();             for (int i = 0; i < ITERATIONS; i++) {                 // ScopedValue是不可变的,需要重新绑定                 String newValue = current + "_" + i;                 ScopedValue.runWhere(SCOPED_VALUE, newValue, () -> {                     // 嵌套作用域                     String nestedValue = SCOPED_VALUE.get();                 });             }         });                  long svEnd = System.nanoTime();                  System.out.printf("ThreadLocal耗时: %d ns%n", tlEnd - tlStart);         System.out.printf("ScopedValue耗时: %d ns%n", svEnd - svStart);     } } 

四、ScopedValue高级特性

有些小伙伴掌握了基础用法后,还想了解更高级的特性。

ScopedValue确实提供了很多强大的功能。

4.1 结构化并发支持

ScopedValue与虚拟线程和结构化并发完美配合:

/**  * ScopedValue与结构化并发  */ public class StructuredConcurrencyExample {          private static final ScopedValue<UserContext> USER_CONTEXT = ScopedValue.newInstance();     private static final ScopedValue<RequestInfo> REQUEST_INFO = ScopedValue.newInstance();          /**      * 结构化并发中的ScopedValue使用      */     public void structuredConcurrencyWithScopedValue() throws Exception {         UserContext user = new UserContext("structured_user");         RequestInfo request = new RequestInfo("req_123", System.currentTimeMillis());                  try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {                          ScopedValue.runWhere(                 ScopedValue.where(USER_CONTEXT, user)                           .where(REQUEST_INFO, request),                 () -> {                     // 在作用域内提交子任务                     Future<String> userTask = scope.fork(this::fetchUserData);                     Future<String> orderTask = scope.fork(this::fetchOrderData);                     Future<String> paymentTask = scope.fork(this::fetchPaymentData);                                          try {                         // 等待所有任务完成                         scope.join();                         scope.throwIfFailed();                                                  // 处理结果                         String userData = userTask.resultNow();                         String orderData = orderTask.resultNow();                         String paymentData = paymentTask.resultNow();                                                  System.out.println("聚合结果: " + userData + ", " + orderData + ", " + paymentData);                                              } catch (InterruptedException | ExecutionException e) {                         Thread.currentThread().interrupt();                         throw new RuntimeException("任务执行失败", e);                     }                 }             );         }     }          private String fetchUserData() {         // 可以访问ScopedValue,无需参数传递         UserContext user = USER_CONTEXT.get();         RequestInfo request = REQUEST_INFO.get();                  return "用户数据: " + user.getUserId() + ", 请求: " + request.getRequestId();     }          private String fetchOrderData() {         UserContext user = USER_CONTEXT.get();         return "订单数据: " + user.getUserId();     }          private String fetchPaymentData() {         UserContext user = USER_CONTEXT.get();         return "支付数据: " + user.getUserId();     } }  class RequestInfo {     private final String requestId;     private final long timestamp;          public RequestInfo(String requestId, long timestamp) {         this.requestId = requestId;         this.timestamp = timestamp;     }          public String getRequestId() { return requestId; }     public long getTimestamp() { return timestamp; } } 

4.2 继承和嵌套作用域

/**  * ScopedValue继承和嵌套  */ public class ScopedValueInheritance {          private static final ScopedValue<String> PARENT_VALUE = ScopedValue.newInstance();     private static final ScopedValue<String> CHILD_VALUE = ScopedValue.newInstance();          /**      * 作用域嵌套      */     public void nestedScopes() {         ScopedValue.runWhere(PARENT_VALUE, "parent_value", () -> {             System.out.println("外层作用域: " + PARENT_VALUE.get());                          // 内层作用域可以访问外层值             ScopedValue.runWhere(CHILD_VALUE, "child_value", () -> {                 System.out.println("内层作用域 - 父值: " + PARENT_VALUE.get());                 System.out.println("内层作用域 - 子值: " + CHILD_VALUE.get());                                  // 可以重新绑定父值(遮蔽)                 ScopedValue.runWhere(PARENT_VALUE, "shadowed_parent", () -> {                     System.out.println("遮蔽作用域 - 父值: " + PARENT_VALUE.get());                     System.out.println("遮蔽作用域 - 子值: " + CHILD_VALUE.get());                 });                                  // 恢复原来的父值                 System.out.println("恢复作用域 - 父值: " + PARENT_VALUE.get());             });                          // 子值已超出作用域             try {                 System.out.println(CHILD_VALUE.get()); // 抛出异常             } catch (Exception e) {                 System.out.println("子值已超出作用域: " + e.getMessage());             }         });     }          /**      * 虚拟线程中的继承      */     public void virtualThreadInheritance() throws Exception {         ScopedValue.runWhere(PARENT_VALUE, "virtual_parent", () -> {             try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {                                  // 虚拟线程自动继承ScopedValue                 for (int i = 0; i < 3; i++) {                     final int taskId = i;                     scope.fork(() -> {                         // 可以访问父线程的ScopedValue                         String parentVal = PARENT_VALUE.get();                         return "任务" + taskId + " - 父值: " + parentVal;                     });                 }                                  scope.join();                 scope.throwIfFailed();             }         });     }          /**      * 条件绑定      */     public void conditionalBinding() {         String condition = Math.random() > 0.5 ? "case_a" : "case_b";                  ScopedValue.runWhere(PARENT_VALUE, condition, () -> {             String value = PARENT_VALUE.get();                          if ("case_a".equals(value)) {                 System.out.println("处理情况A");             } else {                 System.out.println("处理情况B");             }         });     } } 

4.3 错误处理和调试

/**  * ScopedValue错误处理和调试  */ public class ScopedValueErrorHandling {          private static final ScopedValue<String> MAIN_VALUE = ScopedValue.newInstance();     private static final ScopedValue<Integer> COUNT_VALUE = ScopedValue.newInstance();          /**      * 异常处理      */     public void exceptionHandling() {         try {             ScopedValue.runWhere(MAIN_VALUE, "test_value", () -> {                 // 业务逻辑                 processWithError();             });         } catch (RuntimeException e) {             System.out.println("捕获异常: " + e.getMessage());             // ScopedValue已自动清理,无需额外处理         }                  // 验证值已清理         try {             String value = MAIN_VALUE.get();             System.out.println("不应该执行到这里: " + value);         } catch (Exception e) {             System.out.println("值已正确清理: " + e.getMessage());         }     }          /**      * 调试信息      */     public void debugInformation() {         ScopedValue.runWhere(             ScopedValue.where(MAIN_VALUE, "debug_value")                       .where(COUNT_VALUE, 42),             () -> {                 // 获取当前绑定的所有ScopedValue                 System.out.println("当前作用域绑定:");                 System.out.println("MAIN_VALUE: " + MAIN_VALUE.get());                 System.out.println("COUNT_VALUE: " + COUNT_VALUE.get());                                  // 模拟复杂调试                 debugComplexScenario();             }         );     }          /**      * 资源清理保证      */     public void resourceCleanupGuarantee() {         List<String> cleanupLog = new ArrayList<>();                  ScopedValue.runWhere(MAIN_VALUE, "resource_value", () -> {             // 注册清理钩子             Runtime.getRuntime().addShutdownHook(new Thread(() -> {                 cleanupLog.add("资源清理完成");             }));                          // 即使这里发生异常,ScopedValue也会清理             if (Math.random() > 0.5) {                 throw new RuntimeException("模拟异常");             }         });                  // 检查清理情况         System.out.println("清理日志: " + cleanupLog);     }          private void processWithError() {         throw new RuntimeException("业务处理异常");     }          private void debugComplexScenario() {         // 复杂的调试场景         ScopedValue.runWhere(COUNT_VALUE, COUNT_VALUE.get() + 1, () -> {             System.out.println("嵌套调试 - COUNT_VALUE: " + COUNT_VALUE.get());         });     } } 

五、实战案例

有些小伙伴可能还想看更复杂的实战案例。

让我们用一个Web应用中的用户上下文管理来展示ScopedValue在真实项目中的应用。

为了更直观地理解Web应用中ScopedValue的应用,我画了一个请求处理流程的架构图:

再见了ThreadLocal,我决定用ScopedValue!

ScopedValue的生命周期如下图所示:
再见了ThreadLocal,我决定用ScopedValue!

优势如下图所示:

再见了ThreadLocal,我决定用ScopedValue!

5.1 定义Web应用中的ScopedValue

/**  * Web应用ScopedValue定义  */ public class WebScopedValues {          // 用户上下文     public static final ScopedValue<UserContext> USER_CONTEXT = ScopedValue.newInstance();          // 请求信息     public static final ScopedValue<RequestInfo> REQUEST_INFO = ScopedValue.newInstance();          // 数据库连接(可选)     public static final ScopedValue<Connection> DB_CONNECTION = ScopedValue.newInstance();          // 追踪ID     public static final ScopedValue<String> TRACE_ID = ScopedValue.newInstance(); }  /**  * 用户上下文详细信息  */ class UserContext {     private final String userId;     private final String username;     private final List<String> roles;     private final Map<String, Object> attributes;     private final Locale locale;          public UserContext(String userId, String username, List<String> roles,                        Map<String, Object> attributes, Locale locale) {         this.userId = userId;         this.username = username;         this.roles = Collections.unmodifiableList(new ArrayList<>(roles));         this.attributes = Collections.unmodifiableMap(new HashMap<>(attributes));         this.locale = locale;     }          // Getter方法     public String getUserId() { return userId; }     public String getUsername() { return username; }     public List<String> getRoles() { return roles; }     public Map<String, Object> getAttributes() { return attributes; }     public Locale getLocale() { return locale; }          public boolean hasRole(String role) {         return roles.contains(role);     }          public Object getAttribute(String key) {         return attributes.get(key);     } }  /**  * 请求信息  */ class RequestInfo {     private final String requestId;     private final String method;     private final String path;     private final String clientIp;     private final Map<String, String> headers;          public RequestInfo(String requestId, String method, String path,                        String clientIp, Map<String, String> headers) {         this.requestId = requestId;         this.method = method;         this.path = path;         this.clientIp = clientIp;         this.headers = Collections.unmodifiableMap(new HashMap<>(headers));     }          // Getter方法     public String getRequestId() { return requestId; }     public String getMethod() { return method; }     public String getPath() { return path; }     public String getClientIp() { return clientIp; }     public Map<String, String> getHeaders() { return headers; } } 

5.2 过滤器实现

/**  * 认证过滤器 - 使用ScopedValue  */ @Component @Slf4j public class AuthenticationFilter implements Filter {          @Autowired     private UserService userService;          @Autowired     private JwtTokenProvider tokenProvider;          @Override     public void doFilter(ServletRequest request, ServletResponse response,                          FilterChain chain) throws IOException, ServletException {                  HttpServletRequest httpRequest = (HttpServletRequest) request;         HttpServletResponse httpResponse = (HttpServletResponse) response;                  // 生成请求ID         String requestId = generateRequestId();                  // 提取请求信息         RequestInfo requestInfo = extractRequestInfo(httpRequest, requestId);                  // 认证用户         UserContext userContext = authenticateUser(httpRequest);                  // 在作用域内执行请求处理         ScopedValue.runWhere(             ScopedValue.where(WebScopedValues.REQUEST_INFO, requestInfo)                       .where(WebScopedValues.USER_CONTEXT, userContext)                       .where(WebScopedValues.TRACE_ID, requestId),             () -> {                 try {                     chain.doFilter(request, response);                 } catch (Exception e) {                     log.error("请求处理异常", e);                     throw new RuntimeException("过滤器异常", e);                 }             }         );                  // 作用域结束后自动清理所有ScopedValue         log.info("请求处理完成: {}", requestId);     }          private String generateRequestId() {         return "req_" + System.currentTimeMillis() + "_" + ThreadLocalRandom.current().nextInt(1000, 9999);     }          private RequestInfo extractRequestInfo(HttpServletRequest request, String requestId) {         Map<String, String> headers = new HashMap<>();         Enumeration<String> headerNames = request.getHeaderNames();         while (headerNames.hasMoreElements()) {             String headerName = headerNames.nextElement();             headers.put(headerName, request.getHeader(headerName));         }                  return new RequestInfo(             requestId,             request.getMethod(),             request.getRequestURI(),             request.getRemoteAddr(),             headers         );     }          private UserContext authenticateUser(HttpServletRequest request) {         String authHeader = request.getHeader("Authorization");                  if (authHeader != null && authHeader.startsWith("Bearer ")) {             String token = authHeader.substring(7);             return tokenProvider.validateToken(token);         }                  // 返回匿名用户         return new UserContext(             "anonymous",             "Anonymous User",             List.of("GUEST"),             Map.of("source", "web"),             request.getLocale()         );     } } 

5.3 业务层使用

/**  * 用户服务 - 使用ScopedValue  */ @Service @Slf4j @Transactional public class UserService {          @Autowired     private UserRepository userRepository;          @Autowired     private OrderService orderService;          /**      * 获取当前用户信息      */     public UserProfile getCurrentUserProfile() {         UserContext userContext = WebScopedValues.USER_CONTEXT.get();         RequestInfo requestInfo = WebScopedValues.REQUEST_INFO.get();         String traceId = WebScopedValues.TRACE_ID.get();                  log.info("[{}] 获取用户资料: {}", traceId, userContext.getUserId());                  // 根据用户ID查询用户信息         User user = userRepository.findById(userContext.getUserId())             .orElseThrow(() -> new UserNotFoundException("用户不存在: " + userContext.getUserId()));                  // 构建用户资料         return UserProfile.builder()             .userId(user.getId())             .username(user.getUsername())             .email(user.getEmail())             .roles(userContext.getRoles())             .locale(userContext.getLocale())             .lastLogin(user.getLastLoginTime())             .build();     }          /**      * 更新用户信息      */     public void updateUserProfile(UpdateProfileRequest request) {         UserContext userContext = WebScopedValues.USER_CONTEXT.get();         String traceId = WebScopedValues.TRACE_ID.get();                  log.info("[{}] 更新用户资料: {}", traceId, userContext.getUserId());                  // 验证权限         if (!userContext.getUserId().equals(request.getUserId())) {             throw new PermissionDeniedException("无权更新其他用户资料");         }                  // 更新用户信息         User user = userRepository.findById(request.getUserId())             .orElseThrow(() -> new UserNotFoundException("用户不存在: " + request.getUserId()));                  user.setEmail(request.getEmail());         user.setUpdateTime(LocalDateTime.now());                  userRepository.save(user);                  log.info("[{}] 用户资料更新成功: {}", traceId, userContext.getUserId());     }          /**      * 获取用户订单列表      */     public List<Order> getUserOrders() {         UserContext userContext = WebScopedValues.USER_CONTEXT.get();                  // 调用订单服务,无需传递用户ID         return orderService.getUserOrders();     } }  /**  * 订单服务  */ @Service @Slf4j @Transactional public class OrderService {          @Autowired     private OrderRepository orderRepository;          public List<Order> getUserOrders() {         UserContext userContext = WebScopedValues.USER_CONTEXT.get();         String traceId = WebScopedValues.TRACE_ID.get();                  log.info("[{}] 查询用户订单: {}", traceId, userContext.getUserId());                  // 直接从ScopedValue获取用户ID,无需参数传递         return orderRepository.findByUserId(userContext.getUserId());     }          /**      * 创建订单      */     public Order createOrder(CreateOrderRequest request) {         UserContext userContext = WebScopedValues.USER_CONTEXT.get();         String traceId = WebScopedValues.TRACE_ID.get();                  log.info("[{}] 创建订单: 用户={}", traceId, userContext.getUserId());                  // 创建订单         Order order = new Order();         order.setOrderId(generateOrderId());         order.setUserId(userContext.getUserId());         order.setAmount(request.getTotalAmount());         order.setStatus(OrderStatus.CREATED);         order.setCreateTime(LocalDateTime.now());                  Order savedOrder = orderRepository.save(order);                  log.info("[{}] 订单创建成功: {}", traceId, savedOrder.getOrderId());                  return savedOrder;     }          private String generateOrderId() {         return "ORD" + System.currentTimeMillis() + ThreadLocalRandom.current().nextInt(1000, 9999);     } } 

5.4 Controller层

/**  * 用户控制器 - 使用ScopedValue  */ @RestController @RequestMapping("/api/users") @Slf4j public class UserController {          @Autowired     private UserService userService;          /**      * 获取当前用户资料      */     @GetMapping("/profile")     public ResponseEntity<UserProfile> getCurrentUserProfile() {         // 无需传递用户ID,直接从ScopedValue获取         UserProfile profile = userService.getCurrentUserProfile();         return ResponseEntity.ok(profile);     }          /**      * 更新用户资料      */     @PutMapping("/profile")     public ResponseEntity<Void> updateUserProfile(@RequestBody @Valid UpdateProfileRequest request) {         userService.updateUserProfile(request);         return ResponseEntity.ok().build();     }          /**      * 获取用户订单      */     @GetMapping("/orders")     public ResponseEntity<List<Order>> getUserOrders() {         List<Order> orders = userService.getUserOrders();         return ResponseEntity.ok(orders);     }          /**      * 异常处理      */     @ExceptionHandler({UserNotFoundException.class, PermissionDeniedException.class})     public ResponseEntity<ErrorResponse> handleUserExceptions(RuntimeException e) {         // 可以从ScopedValue获取请求信息用于日志         String traceId = WebScopedValues.TRACE_ID.get();         log.error("[{}] 用户操作异常: {}", traceId, e.getMessage());                  ErrorResponse error = new ErrorResponse(             e.getClass().getSimpleName(),             e.getMessage(),             traceId         );                  return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);     } }  /**  * 错误响应  */ @Data @AllArgsConstructor class ErrorResponse {     private String error;     private String message;     private String traceId;     private long timestamp = System.currentTimeMillis(); } 

六、迁移指南:从ThreadLocal到ScopedValue

有些小伙伴可能担心迁移成本,其实从ThreadLocal迁移到ScopedValue并不复杂。

6.1 迁移步骤

/**  * ThreadLocal到ScopedValue迁移指南  */ public class MigrationGuide {          // ThreadLocal定义(旧方式)     private static final ThreadLocal<UserContext> TL_USER = new ThreadLocal<>();     private static final ThreadLocal<Connection> TL_CONN = new ThreadLocal<>();     private static final ThreadLocal<String> TL_TRACE = new ThreadLocal<>();          // ScopedValue定义(新方式)     private static final ScopedValue<UserContext> SV_USER = ScopedValue.newInstance();     private static final ScopedValue<Connection> SV_CONN = ScopedValue.newInstance();     private static final ScopedValue<String> SV_TRACE = ScopedValue.newInstance();          /**      * 迁移前:ThreadLocal方式      */     public void beforeMigration() {         // 设置值         TL_USER.set(new UserContext("user_old"));         TL_TRACE.set("trace_old");                  Connection conn = null;         try {             conn = createConnection();             TL_CONN.set(conn);                          // 业务处理             processBusinessOld();                      } catch (Exception e) {             // 异常处理         } finally {             // 必须手动清理             TL_USER.remove();             TL_TRACE.remove();             TL_CONN.remove();                          if (conn != null) {                 closeConnection(conn);             }         }     }          /**      * 迁移后:ScopedValue方式      */     public void afterMigration() {         UserContext user = new UserContext("user_new");         String trace = "trace_new";                  // 使用try-with-resources管理连接         try (Connection conn = createConnection()) {                          // 在作用域内执行             ScopedValue.runWhere(                 ScopedValue.where(SV_USER, user)                           .where(SV_TRACE, trace)                           .where(SV_CONN, conn),                 () -> {                     // 业务处理                     processBusinessNew();                 }             );                          // 自动清理,无需finally块         } catch (Exception e) {             // 异常处理         }     }          /**      * 业务处理 - 旧方式      */     private void processBusinessOld() {         // 需要处理null值         UserContext user = TL_USER.get();         if (user == null) {             throw new IllegalStateException("用户上下文未设置");         }                  Connection conn = TL_CONN.get();         if (conn == null) {             throw new IllegalStateException("数据库连接未设置");         }                  String trace = TL_TRACE.get();                  // 业务逻辑...         System.out.println("处理用户: " + user.getUserId() + ", 追踪: " + trace);     }          /**      * 业务处理 - 新方式      */     private void processBusinessNew() {         // 在作用域内保证不为null         UserContext user = SV_USER.get();         Connection conn = SV_CONN.get();         String trace = SV_TRACE.get();                  // 业务逻辑...         System.out.println("处理用户: " + user.getUserId() + ", 追踪: " + trace);     }          /**      * 复杂迁移场景:嵌套ThreadLocal      */     public void complexMigration() {         // 旧方式:嵌套ThreadLocal         TL_USER.set(new UserContext("outer_user"));         try {             // 内层逻辑             TL_USER.set(new UserContext("inner_user"));             try {                 processBusinessOld();             } finally {                 // 恢复外层值                 TL_USER.set(new UserContext("outer_user"));             }         } finally {             TL_USER.remove();         }                  // 新方式:嵌套ScopedValue         ScopedValue.runWhere(SV_USER, new UserContext("outer_user"), () -> {             ScopedValue.runWhere(SV_USER, new UserContext("inner_user"), () -> {                 processBusinessNew(); // 使用内层值             });             // 自动恢复外层值             processBusinessNew(); // 使用外层值         });     }          private Connection createConnection() {         // 创建连接         return null;     }          private void closeConnection(Connection conn) {         // 关闭连接     } } 

6.2 兼容性处理

/**  * 兼容性处理 - 逐步迁移  */ public class CompatibilityLayer {          // 新代码使用ScopedValue     private static final ScopedValue<UserContext> SV_USER = ScopedValue.newInstance();          // 旧代码可能还在使用ThreadLocal     private static final ThreadLocal<UserContext> TL_USER = new ThreadLocal<>();          /**      * 桥接模式:同时支持两种方式      */     public void bridgePattern() {         UserContext user = new UserContext("bridge_user");                  // 在新作用域内执行         ScopedValue.runWhere(SV_USER, user, () -> {             // 同时设置ThreadLocal以兼容旧代码             TL_USER.set(user);                          try {                 // 执行业务逻辑(新旧代码都可以工作)                 processMixedBusiness();                              } finally {                 // 清理ThreadLocal                 TL_USER.remove();             }         });     }          /**      * 适配器:让旧代码使用ScopedValue      */     public static class ThreadLocalAdapter {         private final ScopedValue<UserContext> scopedValue;                  public ThreadLocalAdapter(ScopedValue<UserContext> scopedValue) {             this.scopedValue = scopedValue;         }                  public void set(UserContext user) {             // 对于set操作,需要在适当的作用域调用             throw new UnsupportedOperationException("请使用ScopedValue.runWhere");         }                  public UserContext get() {             try {                 return scopedValue.get();             } catch (Exception e) {                 // 如果不在作用域内,返回null(模拟ThreadLocal行为)                 return null;             }         }                  public void remove() {             // 无需操作,ScopedValue自动管理         }     }          /**      * 混合业务处理      */     private void processMixedBusiness() {         // 新代码使用ScopedValue         UserContext svUser = SV_USER.get();         System.out.println("ScopedValue用户: " + svUser.getUserId());                  // 旧代码使用ThreadLocal(通过桥接设置)         UserContext tlUser = TL_USER.get();         System.out.println("ThreadLocal用户: " + tlUser.getUserId());                  // 两者应该相同         assert svUser == tlUser;     }          /**      * 逐步迁移策略      */     public void gradualMigrationStrategy() {         // 阶段1:引入ScopedValue,与ThreadLocal共存         // 阶段2:新代码使用ScopedValue,旧代码逐步迁移         // 阶段3:移除ThreadLocal,完全使用ScopedValue                  System.out.println("建议的迁移阶段:");         System.out.println("1. 引入ScopedValue,建立桥接");         System.out.println("2. 新功能使用ScopedValue");         System.out.println("3. 逐步迁移旧代码");         System.out.println("4. 移除ThreadLocal相关代码");         System.out.println("5. 清理桥接层");     } } 

总结

经过上面的深度剖析,我们来总结一下ScopedValue的核心优势。

核心优势

  1. 内存安全:自动生命周期管理,彻底解决内存泄漏
  2. 使用简单:结构化绑定,无需手动清理
  3. 性能优异:专为虚拟线程优化,性能更好
  4. 并发友好:完美支持结构化并发和虚拟线程

迁移决策指南

有些小伙伴在迁移时可能犹豫不决,我总结了一个决策指南:

ThreadLocal vs ScopedValue 选择口诀

  • 新项目:直接使用ScopedValue,享受现代特性
  • 老项目:逐步迁移,先在新模块使用
  • 性能敏感:ScopedValue在虚拟线程中表现更佳
  • 内存敏感:ScopedValue无内存泄漏风险
  • 团队技能:ThreadLocal更普及,ScopedValue需要学习

技术对比

特性 ThreadLocal ScopedValue
内存管理 手动remove 自动管理
内存泄漏 高风险 无风险
使用复杂度 高(需要try-finally) 低(结构化绑定)
性能 较好 更优(虚拟线程)
继承性 需要InheritableThreadLocal 自动继承
虚拟线程支持 有问题 完美支持

最后的建议

ScopedValue是Java并发编程的重要进步,我建议大家:

  1. 学习掌握:尽快学习掌握ScopedValue的使用
  2. 新项目首选:在新项目中优先使用ScopedValue
  3. 逐步迁移:在老项目中制定合理的迁移计划
  4. 关注生态:关注相关框架对ScopedValue的支持

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。

更多项目实战在我的技术网站:http://www.susan.net.cn/project

发表评论

评论已关闭。

相关文章