再见了ThreadLocal,我决定用ScopedValue!
前言
今天我们来聊聊一个即将改变我们编程习惯的新特性——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++) {
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问题的根本原因
- 生命周期管理复杂:需要手动调用set/remove,容易遗漏
- 内存泄漏风险:线程池中线程复用,Value无法被GC
- 继承性差:子线程无法自动继承父线程数据
- 性能开销: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()) {
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的内存模型图:

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) {
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的核心优势。
核心优势
- 内存安全:自动生命周期管理,彻底解决内存泄漏
- 使用简单:结构化绑定,无需手动清理
- 性能优异:专为虚拟线程优化,性能更好
- 并发友好:完美支持结构化并发和虚拟线程
迁移决策指南
有些小伙伴在迁移时可能犹豫不决,我总结了一个决策指南:
ThreadLocal vs ScopedValue 选择口诀
- 新项目:直接使用ScopedValue,享受现代特性
- 老项目:逐步迁移,先在新模块使用
- 性能敏感:ScopedValue在虚拟线程中表现更佳
- 内存敏感:ScopedValue无内存泄漏风险
- 团队技能:ThreadLocal更普及,ScopedValue需要学习
技术对比
| 特性 | ThreadLocal | ScopedValue |
|---|---|---|
| 内存管理 | 手动remove | 自动管理 |
| 内存泄漏 | 高风险 | 无风险 |
| 使用复杂度 | 高(需要try-finally) | 低(结构化绑定) |
| 性能 | 较好 | 更优(虚拟线程) |
| 继承性 | 需要InheritableThreadLocal | 自动继承 |
| 虚拟线程支持 | 有问题 | 完美支持 |
最后的建议
ScopedValue是Java并发编程的重要进步,我建议大家:
- 学习掌握:尽快学习掌握ScopedValue的使用
- 新项目首选:在新项目中优先使用ScopedValue
- 逐步迁移:在老项目中制定合理的迁移计划
- 关注生态:关注相关框架对ScopedValue的支持
最后说一句(求关注,别白嫖我)
如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。
更多项目实战在我的技术网站:http://www.susan.net.cn/project

浙公网安备 33010602011771号