⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 苏三说技术 「苏三」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

前言

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

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

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

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

一、ThreadLocal的痛点

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

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

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

ThreadLocal的内存泄漏问题

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

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++) {
finalint 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的架构图:

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()) {
thrownew 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的内存模型图:

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的应用,我画了一个请求处理流程的架构图:

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) {
thrownew IllegalStateException("用户上下文未设置");
}

Connection conn = TL_CONN.get();
if (conn == null) {
thrownew 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行为)
returnnull;
}
}

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的支持
文章目录
  1. 1. 前言
  2. 2. 一、ThreadLocal的痛点
    1. 2.1. ThreadLocal的内存泄漏问题
    2. 2.2. ThreadLocal的典型问题代码
    3. 2.3. ThreadLocal问题的根本原因
  3. 3. 二、ScopedValue:新一代线程局部变量
    1. 3.1. ScopedValue的核心设计理念
    2. 3.2. ScopedValue基础用法
  4. 4. 三、ScopedValue vs ThreadLocal:全面对比
    1. 4.1. 3.1 内存管理对比
    2. 4.2. 3.2 代码对比示例
    3. 4.3. 3.3 性能对比测试
  5. 5. 四、ScopedValue高级特性
    1. 5.1. 4.1 结构化并发支持
    2. 5.2. 4.2 继承和嵌套作用域
    3. 5.3. 4.3 错误处理和调试
  6. 6. 五、实战案例
    1. 6.1. 5.1 定义Web应用中的ScopedValue
    2. 6.2. 5.2 过滤器实现
    3. 6.3. 5.3 业务层使用
    4. 6.4. 5.4 Controller层
  7. 7. 六、迁移指南:从ThreadLocal到ScopedValue
    1. 7.1. 6.1 迁移步骤
    2. 7.2. 6.2 兼容性处理
  8. 8. 总结
    1. 8.1. 核心优势
    2. 8.2. 迁移决策指南
    3. 8.3. 技术对比
    4. 8.4. 最后的建议