Scoped Values 是一種在特定執行範圍內(scope)安全地共享不可變資料的方法。主要用來取代 ThreadLocal,適合用在 Virtual Thread。
ThreadLocal vs ScopedValue
ThreadLocal
為每個 Thread 儲存獨立的可變狀態,每個
Thread都有自己的ThreadLocalMap。ThreadLocal狀態同步代價高傳統、可變、不安全於虛擬執行緒
需要手動清理 (
remove())同一 Thread 被reuse(例如在 Executor 裡)會殘留前一次的資料。
使用 Thread Pool 或 Virtual Thread 時容易出錯
每個 虛擬執行緒 都有自己的 ThreadLocal Map
ScopedValue
在限定作用範圍中傳遞不可變資料
不可變、安全、快速
易於清理,離開 scope 即失效。結束作用域後自動清理
支援虛擬執行緒、巢狀範圍與 Structured Concurrency
Demo
如果你希望把 ThreadLocal 值「傳遞」給虛擬執行緒,有兩種方法:
手動傳遞:在建立虛擬執行緒時,讀取父 ThreadLocal,然後在子執行緒裡設置。
使用 InheritableThreadLocal:Java 提供
InheritableThreadLocal,可以讓子執行緒自動繼承父執行緒的值。- 注意:對 虛擬執行緒,InheritableThreadLocal 仍然可用,但它只在 創建時 繼承,後續變更不會同步。
import java.util.concurrent.*;
import java.lang.ScopedValue;
import java.time.Duration;
public class ScopedValueVsThreadLocalDemo {
private static final ThreadLocal<String> TL_USER = new ThreadLocal<>();
private static final ScopedValue<String> SV_USER = ScopedValue.newInstance();
private static final InheritableThreadLocal<String> ITL_USER = new InheritableThreadLocal<>();
public static void main(String[] args) throws Exception {
System.out.println("\n=== ThreadLocal 測試 ===");
testThreadLocal();
System.out.println("\n=== ThreadLocal 測試2 ===");
testThreadLocal2();
System.out.println("\n=== InheritableThreadLocal 測試3 ===");
testThreadLocal3();
System.out.println("\n=== ScopedValue 測試 ===");
testScopedValue();
}
private static void testThreadLocal() throws Exception {
TL_USER.set("Alice");
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var future = executor.submit(() -> {
// ThreadLocal 值在 virtual thread 裏面,不會自動繼承
System.out.println("[VirtualThread] USER = " + TL_USER.get());
TL_USER.set("Cat");
TL_USER.remove();
return null;
});
future.get();
}
// 離開 VirtualThread 範圍後再取值 -> 還是存在,且已被修改
System.out.println("[MainThread] USER = " + TL_USER.get());
TL_USER.remove();
}
private static void testThreadLocal2() throws Exception {
TL_USER.set("Alice");
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 讀取父 ThreadLocal
String parentValue = TL_USER.get();
// 手動傳遞給虛擬執行緒
var future = executor.submit(() -> {
TL_USER.set(parentValue); // 設定子執行緒的值
System.out.println("[Child VT] USER = " + TL_USER.get());
TL_USER.set("Cat");
TL_USER.remove();
return null;
});
future.get();
}
System.out.println("[MainThread] USER = " + TL_USER.get());
TL_USER.remove();
}
private static void testThreadLocal3() throws Exception {
ITL_USER.set("Bob");
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var future = executor.submit(() -> {
System.out.println("[Child VT] ITL_USER = " + ITL_USER.get());
return null;
});
future.get();
}
System.out.println("[MainThread] ITL_USER = " + ITL_USER.get());
TL_USER.remove();
}
private static void testScopedValue() throws Exception {
ScopedValue.where(SV_USER, "Bob").run(() -> {
System.out.println("[MainScope] USER = " + SV_USER.get());
try (var scope = StructuredTaskScope.open()) {
scope.fork(() -> {
System.out.println("[Child VT] USER = " + SV_USER.get());
return null;
});
scope.join();
} catch (InterruptedException ie) {
System.err.println("StructuredTaskScope: " + ie);
}
});
// 離開 ScopedValue 範圍後再取值 -> 會丟例外
try {
System.out.println("[After Scope] USER = " + SV_USER.get());
} catch (Exception e) {
System.out.println("[After Scope] 無法取得,因為作用域已結束: " + e);
}
}
}
編譯,要加上參數
javac --enable-preview --release 25 ScopedValueVsThreadLocalDemo.java
執行
java --enable-preview ScopedValueVsThreadLocalDemo
結果
=== ThreadLocal 測試 ===
[VirtualThread] USER = null
[MainThread] USER = Alice
=== ThreadLocal 測試2 ===
[Child VT] USER = Alice
[MainThread] USER = Alice
=== InheritableThreadLocal 測試3 ===
[Child VT] ITL_USER = Bob
[MainThread] ITL_USER = Bob
=== ScopedValue 測試 ===
[MainScope] USER = Bob
[Child VT] USER = Bob
[After Scope] 無法取得,因為作用域已結束: java.util.NoSuchElementException: ScopedValue not bound