別再用ThreadLocal了,ScopedValue更香!
前言
今天我們來聊聊一個即將改變我們編程習(xí)慣的新特性——ScopedValue。
有些小伙伴在工作中,一提到線程內(nèi)數(shù)據(jù)傳遞就想到ThreadLocal,但真正用起來卻遇到各種坑:內(nèi)存泄漏、數(shù)據(jù)污染、性能問題等等。
其實,ScopedValue就像ThreadLocal的升級版,既保留了優(yōu)點,又解決了痛點。
我們一起聊聊ScopedValue的優(yōu)勢和用法,希望對你會有所幫助。
一、ThreadLocal的痛點
在介紹ScopedValue之前,我們先回顧一下ThreadLocal的常見問題。
有些小伙伴可能會想:"ThreadLocal用得好好的,為什么要換?"
其實,ThreadLocal在設(shè)計上存在一些固有缺陷。
ThreadLocal的內(nèi)存泄漏問題
為了更直觀地理解ThreadLocal的內(nèi)存泄漏問題,我畫了一個內(nèi)存泄漏的示意圖:

ThreadLocal的典型問題代碼
/**
* ThreadLocal典型問題演示
*/
public class ThreadLocalProblems {
private static final ThreadLocal<UserContext> userContext = new ThreadLocal<>();
private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
/**
* 問題1:內(nèi)存泄漏 - 忘記調(diào)用remove()
*/
public void processRequest(HttpServletRequest request) {
// 設(shè)置用戶上下文
UserContext context = new UserContext(request.getHeader("X-User-Id"));
userContext.set(context);
try {
// 業(yè)務(wù)處理
businessService.process();
// 問題:忘記調(diào)用 userContext.remove()
// 在線程池中,這個線程被重用時,還會保留之前的用戶信息
} catch (Exception e) {
// 異常處理
}
}
/**
* 問題2:數(shù)據(jù)污染 - 線程復(fù)用導(dǎo)致數(shù)據(jù)混亂
*/
public void processMultipleRequests() {
// 線程池處理多個請求
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
finalint userId = i;
executor.submit(() -> {
// 設(shè)置用戶上下文
userContext.set(new UserContext("user_" + userId));
try {
// 模擬業(yè)務(wù)處理
Thread.sleep(100);
// 問題:如果線程被復(fù)用,這里可能讀取到錯誤的用戶信息
String currentUser = userContext.get().getUserId();
System.out.println("處理用戶: " + currentUser);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 即使調(diào)用remove,也可能因為異常跳過
userContext.remove(); // 不保證一定執(zhí)行
}
});
}
executor.shutdown();
}
/**
* 問題3:繼承性問題 - 子線程無法繼承父線程數(shù)據(jù)
*/
public void parentChildThreadProblem() {
userContext.set(new UserContext("parent_user"));
Thread childThread = new Thread(() -> {
// 這里獲取不到父線程的ThreadLocal值
UserContext context = userContext.get(); // null
System.out.println("子線程用戶: " + context); // 輸出null
// 需要手動傳遞數(shù)據(jù)
});
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問題的根本原因
- 生命周期管理復(fù)雜:需要手動調(diào)用set/remove,容易遺漏
- 內(nèi)存泄漏風(fēng)險:線程池中線程復(fù)用,Value無法被GC
- 繼承性差:子線程無法自動繼承父線程數(shù)據(jù)
- 性能開銷:ThreadLocalMap的哈希表操作有開銷
有些小伙伴可能會問:"我們用InheritableThreadLocal不就能解決繼承問題了嗎?"
我的經(jīng)驗是:InheritableThreadLocal只是緩解了問題,但帶來了新的復(fù)雜度,而且性能更差。
二、ScopedValue:新一代線程局部變量
ScopedValue是Java 20中引入的預(yù)覽特性,在Java 21中成為正式特性。
它旨在解決ThreadLocal的痛點,提供更安全、更高效的線程內(nèi)數(shù)據(jù)傳遞方案。
ScopedValue的核心設(shè)計理念
為了更直觀地理解ScopedValue的工作原理,我畫了一個ScopedValue的架構(gòu)圖:
圖片
ScopedValue的核心優(yōu)勢:
圖片
ScopedValue基礎(chǔ)用法
/**
* ScopedValue基礎(chǔ)用法演示
*/
public class ScopedValueBasics {
// 1. 定義ScopedValue(相當(dāng)于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();
/**
* 基礎(chǔ)用法:在作用域內(nèi)使用ScopedValue
*/
public void basicUsage() {
UserContext user = new UserContext("user_123");
// 在作用域內(nèi)綁定值
ScopedValue.runWhere(USER_CONTEXT, user, () -> {
// 在這個作用域內(nèi),USER_CONTEXT.get()返回user_123
System.out.println("當(dāng)前用戶: " + 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已經(jīng)超出作用域,獲取會拋出異常
});
// 這里USER_CONTEXT已經(jīng)超出作用域
}
/**
* 帶返回值的作用域
*/
public String scopedValueWithReturn() {
UserContext user = new UserContext("user_789");
// 使用callWhere獲取返回值
String result = ScopedValue.callWhere(USER_CONTEXT, user, () -> {
// 業(yè)務(wù)處理
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"),
() -> {
// 在這個作用域內(nèi)可以訪問所有綁定的值
processBusinessLogic();
}
);
// 作用域結(jié)束后自動清理
}
/**
* 異常處理示例
*/
public void exceptionHandling() {
UserContext user = new UserContext("user_exception");
try {
ScopedValue.runWhere(USER_CONTEXT, user, () -> {
// 業(yè)務(wù)處理
processBusinessLogic();
// 如果拋出異常,作用域也會正常結(jié)束
if (someCondition()) {
thrownew RuntimeException("業(yè)務(wù)異常");
}
});
} catch (RuntimeException e) {
// 異常處理
System.out.println("捕獲異常: " + e.getMessage());
}
// 即使發(fā)生異常,USER_CONTEXT也會自動清理
}
private Connection createConnection() {
// 創(chuàng)建數(shù)據(jù)庫連接
return null;
}
private void processBusinessLogic() {
// 業(yè)務(wù)邏輯處理
UserContext user = USER_CONTEXT.get();
System.out.println("處理業(yè)務(wù)邏輯,用戶: " + user.getUserId());
}
private boolean someCondition() {
return Math.random() > 0.5;
}
}三、ScopedValue vs ThreadLocal:全面對比
有些小伙伴可能還想知道ScopedValue到底比ThreadLocal強在哪里。
讓我們通過詳細的對比來看看。
3.1 內(nèi)存管理對比
為了更直觀地理解兩者的內(nèi)存管理差異,我畫了幾張圖做對比。
ThreadLocal的內(nèi)存模型圖:
圖片
ScopedValue的內(nèi)存模型圖:
圖片
二者的關(guān)鍵差異如下圖:
圖片
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方式 - 傳統(tǒng)實現(xiàn)
*/
public void processRequestThreadLocal(HttpServletRequest request) {
// 設(shè)置上下文
UserContext userContext = new UserContext(request.getHeader("X-User-Id"));
TL_USER_CONTEXT.set(userContext);
Connection conn = null;
try {
// 獲取數(shù)據(jù)庫連接
conn = dataSource.getConnection();
TL_CONNECTION.set(conn);
// 業(yè)務(wù)處理
processBusinessLogic();
} catch (SQLException e) {
// 異常處理
handleException(e);
} finally {
// 必須手動清理 - 容易忘記!
TL_USER_CONTEXT.remove();
TL_CONNECTION.remove();
// 關(guān)閉連接
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// 日志記錄
}
}
}
}
/**
* ScopedValue方式 - 現(xiàn)代實現(xiàn)
*/
public void processRequestScopedValue(HttpServletRequest request) {
UserContext userContext = new UserContext(request.getHeader("X-User-Id"));
// 使用try-with-resources管理連接
try (Connection conn = dataSource.getConnection()) {
// 在作用域內(nèi)執(zhí)行,自動管理生命周期
ScopedValue.runWhere(
ScopedValue.where(SV_USER_CONTEXT, userContext)
.where(SV_CONNECTION, conn),
() -> {
// 業(yè)務(wù)處理
processBusinessLogic();
}
);
// 作用域結(jié)束后自動清理,無需手動remove
} catch (SQLException e) {
handleException(e);
}
}
/**
* 業(yè)務(wù)邏輯處理 - 兩種方式對比
*/
private void processBusinessLogic() {
// ThreadLocal方式 - 需要處理null值
UserContext tlUser = TL_USER_CONTEXT.get();
if (tlUser == null) {
throw new IllegalStateException("用戶上下文未設(shè)置");
}
Connection tlConn = TL_CONNECTION.get();
if (tlConn == null) {
throw new IllegalStateException("數(shù)據(jù)庫連接未設(shè)置");
}
// ScopedValue方式 - 在作用域內(nèi)保證不為null
UserContext svUser = SV_USER_CONTEXT.get(); // 不會為null
Connection svConn = SV_CONNECTION.get(); // 不會為null
// 實際業(yè)務(wù)處理...
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; // 模擬數(shù)據(jù)源
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高級特性
有些小伙伴掌握了基礎(chǔ)用法后,還想了解更高級的特性。
ScopedValue確實提供了很多強大的功能。
4.1 結(jié)構(gòu)化并發(fā)支持
ScopedValue與虛擬線程和結(jié)構(gòu)化并發(fā)完美配合:
/**
* ScopedValue與結(jié)構(gòu)化并發(fā)
*/
public class StructuredConcurrencyExample {
private static final ScopedValue<UserContext> USER_CONTEXT = ScopedValue.newInstance();
private static final ScopedValue<RequestInfo> REQUEST_INFO = ScopedValue.newInstance();
/**
* 結(jié)構(gòu)化并發(fā)中的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),
() -> {
// 在作用域內(nèi)提交子任務(wù)
Future<String> userTask = scope.fork(this::fetchUserData);
Future<String> orderTask = scope.fork(this::fetchOrderData);
Future<String> paymentTask = scope.fork(this::fetchPaymentData);
try {
// 等待所有任務(wù)完成
scope.join();
scope.throwIfFailed();
// 處理結(jié)果
String userData = userTask.resultNow();
String orderData = orderTask.resultNow();
String paymentData = paymentTask.resultNow();
System.out.println("聚合結(jié)果: " + userData + ", " + orderData + ", " + paymentData);
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("任務(wù)執(zhí)行失敗", e);
}
}
);
}
}
private String fetchUserData() {
// 可以訪問ScopedValue,無需參數(shù)傳遞
UserContext user = USER_CONTEXT.get();
RequestInfo request = REQUEST_INFO.get();
return "用戶數(shù)據(jù): " + user.getUserId() + ", 請求: " + request.getRequestId();
}
private String fetchOrderData() {
UserContext user = USER_CONTEXT.get();
return "訂單數(shù)據(jù): " + user.getUserId();
}
private String fetchPaymentData() {
UserContext user = USER_CONTEXT.get();
return "支付數(shù)據(jù): " + 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());
// 內(nèi)層作用域可以訪問外層值
ScopedValue.runWhere(CHILD_VALUE, "child_value", () -> {
System.out.println("內(nèi)層作用域 - 父值: " + PARENT_VALUE.get());
System.out.println("內(nèi)層作用域 - 子值: " + CHILD_VALUE.get());
// 可以重新綁定父值(遮蔽)
ScopedValue.runWhere(PARENT_VALUE, "shadowed_parent", () -> {
System.out.println("遮蔽作用域 - 父值: " + PARENT_VALUE.get());
System.out.println("遮蔽作用域 - 子值: " + CHILD_VALUE.get());
});
// 恢復(fù)原來的父值
System.out.println("恢復(fù)作用域 - 父值: " + 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 "任務(wù)" + 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 錯誤處理和調(diào)試
/**
* ScopedValue錯誤處理和調(diào)試
*/
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", () -> {
// 業(yè)務(wù)邏輯
processWithError();
});
} catch (RuntimeException e) {
System.out.println("捕獲異常: " + e.getMessage());
// ScopedValue已自動清理,無需額外處理
}
// 驗證值已清理
try {
String value = MAIN_VALUE.get();
System.out.println("不應(yīng)該執(zhí)行到這里: " + value);
} catch (Exception e) {
System.out.println("值已正確清理: " + e.getMessage());
}
}
/**
* 調(diào)試信息
*/
public void debugInformation() {
ScopedValue.runWhere(
ScopedValue.where(MAIN_VALUE, "debug_value")
.where(COUNT_VALUE, 42),
() -> {
// 獲取當(dāng)前綁定的所有ScopedValue
System.out.println("當(dāng)前作用域綁定:");
System.out.println("MAIN_VALUE: " + MAIN_VALUE.get());
System.out.println("COUNT_VALUE: " + COUNT_VALUE.get());
// 模擬復(fù)雜調(diào)試
debugComplexScenario();
}
);
}
/**
* 資源清理保證
*/
public void resourceCleanupGuarantee() {
List<String> cleanupLog = new ArrayList<>();
ScopedValue.runWhere(MAIN_VALUE, "resource_value", () -> {
// 注冊清理鉤子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
cleanupLog.add("資源清理完成");
}));
// 即使這里發(fā)生異常,ScopedValue也會清理
if (Math.random() > 0.5) {
throw new RuntimeException("模擬異常");
}
});
// 檢查清理情況
System.out.println("清理日志: " + cleanupLog);
}
private void processWithError() {
throw new RuntimeException("業(yè)務(wù)處理異常");
}
private void debugComplexScenario() {
// 復(fù)雜的調(diào)試場景
ScopedValue.runWhere(COUNT_VALUE, COUNT_VALUE.get() + 1, () -> {
System.out.println("嵌套調(diào)試 - COUNT_VALUE: " + COUNT_VALUE.get());
});
}
}五、實戰(zhàn)案例
有些小伙伴可能還想看更復(fù)雜的實戰(zhàn)案例。
讓我們用一個Web應(yīng)用中的用戶上下文管理來展示ScopedValue在真實項目中的應(yīng)用。
為了更直觀地理解Web應(yīng)用中ScopedValue的應(yīng)用,我畫了一個請求處理流程的架構(gòu)圖:
圖片
ScopedValue的生命周期如下圖所示:
圖片
優(yōu)勢如下圖所示:
圖片
5.1 定義Web應(yīng)用中的ScopedValue
/**
* Web應(yīng)用ScopedValue定義
*/
public class WebScopedValues {
// 用戶上下文
public static final ScopedValue<UserContext> USER_CONTEXT = ScopedValue.newInstance();
// 請求信息
public static final ScopedValue<RequestInfo> REQUEST_INFO = ScopedValue.newInstance();
// 數(shù)據(jù)庫連接(可選)
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 過濾器實現(xiàn)
/**
* 認證過濾器 - 使用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);
// 在作用域內(nèi)執(zhí)行請求處理
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);
}
}
);
// 作用域結(jié)束后自動清理所有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 業(yè)務(wù)層使用
/**
* 用戶服務(wù) - 使用ScopedValue
*/
@Service
@Slf4j
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private OrderService orderService;
/**
* 獲取當(dāng)前用戶信息
*/
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());
// 根據(jù)用戶ID查詢用戶信息
User user = userRepository.findById(userContext.getUserId())
.orElseThrow(() -> new UserNotFoundException("用戶不存在: " + userContext.getUserId()));
// 構(gòu)建用戶資料
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());
// 驗證權(quán)限
if (!userContext.getUserId().equals(request.getUserId())) {
throw new PermissionDeniedException("無權(quán)更新其他用戶資料");
}
// 更新用戶信息
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();
// 調(diào)用訂單服務(wù),無需傳遞用戶ID
return orderService.getUserOrders();
}
}
/**
* 訂單服務(wù)
*/
@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,無需參數(shù)傳遞
return orderRepository.findByUserId(userContext.getUserId());
}
/**
* 創(chuàng)建訂單
*/
public Order createOrder(CreateOrderRequest request) {
UserContext userContext = WebScopedValues.USER_CONTEXT.get();
String traceId = WebScopedValues.TRACE_ID.get();
log.info("[{}] 創(chuàng)建訂單: 用戶={}", traceId, userContext.getUserId());
// 創(chuàng)建訂單
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("[{}] 訂單創(chuàng)建成功: {}", 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;
/**
* 獲取當(dāng)前用戶資料
*/
@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);
}
}
/**
* 錯誤響應(yīng)
*/
@Data
@AllArgsConstructor
class ErrorResponse {
private String error;
private String message;
private String traceId;
private long timestamp = System.currentTimeMillis();
}六、遷移指南:從ThreadLocal到ScopedValue
有些小伙伴可能擔(dān)心遷移成本,其實從ThreadLocal遷移到ScopedValue并不復(fù)雜。
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() {
// 設(shè)置值
TL_USER.set(new UserContext("user_old"));
TL_TRACE.set("trace_old");
Connection conn = null;
try {
conn = createConnection();
TL_CONN.set(conn);
// 業(yè)務(wù)處理
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()) {
// 在作用域內(nèi)執(zhí)行
ScopedValue.runWhere(
ScopedValue.where(SV_USER, user)
.where(SV_TRACE, trace)
.where(SV_CONN, conn),
() -> {
// 業(yè)務(wù)處理
processBusinessNew();
}
);
// 自動清理,無需finally塊
} catch (Exception e) {
// 異常處理
}
}
/**
* 業(yè)務(wù)處理 - 舊方式
*/
private void processBusinessOld() {
// 需要處理null值
UserContext user = TL_USER.get();
if (user == null) {
thrownew IllegalStateException("用戶上下文未設(shè)置");
}
Connection conn = TL_CONN.get();
if (conn == null) {
thrownew IllegalStateException("數(shù)據(jù)庫連接未設(shè)置");
}
String trace = TL_TRACE.get();
// 業(yè)務(wù)邏輯...
System.out.println("處理用戶: " + user.getUserId() + ", 追蹤: " + trace);
}
/**
* 業(yè)務(wù)處理 - 新方式
*/
private void processBusinessNew() {
// 在作用域內(nèi)保證不為null
UserContext user = SV_USER.get();
Connection conn = SV_CONN.get();
String trace = SV_TRACE.get();
// 業(yè)務(wù)邏輯...
System.out.println("處理用戶: " + user.getUserId() + ", 追蹤: " + trace);
}
/**
* 復(fù)雜遷移場景:嵌套ThreadLocal
*/
public void complexMigration() {
// 舊方式:嵌套ThreadLocal
TL_USER.set(new UserContext("outer_user"));
try {
// 內(nèi)層邏輯
TL_USER.set(new UserContext("inner_user"));
try {
processBusinessOld();
} finally {
// 恢復(fù)外層值
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(); // 使用內(nèi)層值
});
// 自動恢復(fù)外層值
processBusinessNew(); // 使用外層值
});
}
private Connection createConnection() {
// 創(chuàng)建連接
return null;
}
private void closeConnection(Connection conn) {
// 關(guān)閉連接
}
}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");
// 在新作用域內(nèi)執(zhí)行
ScopedValue.runWhere(SV_USER, user, () -> {
// 同時設(shè)置ThreadLocal以兼容舊代碼
TL_USER.set(user);
try {
// 執(zhí)行業(yè)務(wù)邏輯(新舊代碼都可以工作)
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操作,需要在適當(dāng)?shù)淖饔糜蛘{(diào)用
throw new UnsupportedOperationException("請使用ScopedValue.runWhere");
}
public UserContext get() {
try {
return scopedValue.get();
} catch (Exception e) {
// 如果不在作用域內(nèi),返回null(模擬ThreadLocal行為)
returnnull;
}
}
public void remove() {
// 無需操作,ScopedValue自動管理
}
}
/**
* 混合業(yè)務(wù)處理
*/
private void processMixedBusiness() {
// 新代碼使用ScopedValue
UserContext svUser = SV_USER.get();
System.out.println("ScopedValue用戶: " + svUser.getUserId());
// 舊代碼使用ThreadLocal(通過橋接設(shè)置)
UserContext tlUser = TL_USER.get();
System.out.println("ThreadLocal用戶: " + tlUser.getUserId());
// 兩者應(yīng)該相同
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相關(guān)代碼");
System.out.println("5. 清理橋接層");
}
}總結(jié)
經(jīng)過上面的深度剖析,我們來總結(jié)一下ScopedValue的核心優(yōu)勢。
核心優(yōu)勢
- 內(nèi)存安全:自動生命周期管理,徹底解決內(nèi)存泄漏
- 使用簡單:結(jié)構(gòu)化綁定,無需手動清理
- 性能優(yōu)異:專為虛擬線程優(yōu)化,性能更好
- 并發(fā)友好:完美支持結(jié)構(gòu)化并發(fā)和虛擬線程
遷移決策指南
有些小伙伴在遷移時可能猶豫不決,我總結(jié)了一個決策指南:
ThreadLocal vs ScopedValue 選擇口訣
- 新項目:直接使用ScopedValue,享受現(xiàn)代特性
- 老項目:逐步遷移,先在新模塊使用
- 性能敏感:ScopedValue在虛擬線程中表現(xiàn)更佳
- 內(nèi)存敏感:ScopedValue無內(nèi)存泄漏風(fēng)險
- 團隊技能:ThreadLocal更普及,ScopedValue需要學(xué)習(xí)
技術(shù)對比
特性 | ThreadLocal | ScopedValue |
內(nèi)存管理 | 手動remove | 自動管理 |
內(nèi)存泄漏 | 高風(fēng)險 | 無風(fēng)險 |
使用復(fù)雜度 | 高(需要try-finally) | 低(結(jié)構(gòu)化綁定) |
性能 | 較好 | 更優(yōu)(虛擬線程) |
繼承性 | 需要InheritableThreadLocal | 自動繼承 |
虛擬線程支持 | 有問題 | 完美支持 |
最后的建議
ScopedValue是Java并發(fā)編程的重要進步,我建議大家:
- 學(xué)習(xí)掌握:盡快學(xué)習(xí)掌握ScopedValue的使用
- 新項目首選:在新項目中優(yōu)先使用ScopedValue
- 逐步遷移:在老項目中制定合理的遷移計劃
- 關(guān)注生態(tài):關(guān)注相關(guān)框架對ScopedValue的支持

































