2026/03/30

Scoped Values

Scoped Values 是一種在特定執行範圍內(scope)安全地共享不可變資料的方法。主要用來取代 ThreadLocal,適合用在 Virtual Thread。

ThreadLocal vs ScopedValue

ThreadLocal

  • 為每個 Thread 儲存獨立的可變狀態,每個 Thread 都有自己的ThreadLocalMap。

  • ThreadLocal 狀態同步代價高

  • 傳統、可變、不安全於虛擬執行緒

  • 需要手動清理 (remove())

  • 同一 Thread 被reuse(例如在 Executor 裡)會殘留前一次的資料。

  • 使用 Thread PoolVirtual Thread 時容易出錯

  • 每個 虛擬執行緒 都有自己的 ThreadLocal Map

ScopedValue

  • 在限定作用範圍中傳遞不可變資料

  • 不可變、安全、快速

  • 易於清理,離開 scope 即失效。結束作用域後自動清理

  • 支援虛擬執行緒、巢狀範圍與 Structured Concurrency

Demo

如果你希望把 ThreadLocal 值「傳遞」給虛擬執行緒,有兩種方法:

  1. 手動傳遞:在建立虛擬執行緒時,讀取父 ThreadLocal,然後在子執行緒裡設置。

  2. 使用 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

沒有留言:

張貼留言