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

2026/03/23

Structure Concurrency

是一種讓多執行緒邏輯變得更容易理解與管理的程式設計模型,目標是讓「並行任務的生命週期」像區塊結構(block structure)一樣有明確的範圍。

  • 子任務(threads, tasks)都必須在離開某個區塊前結束

  • 執行緒之間的層級關係(parent/child)是語法上明確可見的。

  • 不會有「孤兒執行緒」(dangling thread)偷偷跑在背景。

傳統的 thread

  • 任務彼此獨立、缺乏邏輯關聯。

  • 錯誤傳遞困難(子執行緒例外不會自動向上傳遞)。

  • 難以在結束前確保所有子任務完成。

void process() {
    Thread t = new Thread(() -> downloadFile());
    t.start();
    // ... do other work ...
    // 忘記 join() 或沒有捕捉例外,就可能造成 thread 泄漏
}

Structure Concurrency (JEP 453 / Java 21)

  • ShutdownOnFailure: 若任一子任務失敗,其他會自動中止。

  • ShutdownOnSuccess: 若有一個成功,其餘終止(常用於競賽式查詢)。

  • 範圍清晰、錯誤可控、資源可預期。

import java.util.concurrent.*;

void process() throws Exception {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Future<String> f1 = scope.fork(() -> downloadFile());
        Future<String> f2 = scope.fork(() -> fetchMetadata());
        scope.join();              // 等待所有子任務
        scope.throwIfFailed();     // 傳遞例外
        System.out.println(f1.resultNow() + f2.resultNow());
    } // scope 區塊結束時,自動關閉所有未完成子任務
}
  • Virtual Threads 解決「效能 & 可擴充性」。

  • Structured Concurrency 解決「邏輯一致性 & 錯誤處理」。

Example

import java.util.concurrent.*;
import java.lang.management.*;

public class StructuredConcurrencyVirtualThreadsExample {

    public static void main(String[] args) throws Exception {
        System.out.println("Java: " + System.getProperty("java.version"));
        System.out.println();

        System.out.println("=== Example 1: Structured Concurrency (ShutdownOnFailure) ===");
        example();

        System.out.println();
        System.out.println("=== Example 2: Virtual vs Native Thread Performance Test ===");
        compareVirtualAndNativeThreads();
    }

    static void example() {
        // 使用 StructuredTaskScope.open() 創建作用域
        try (var scope = StructuredTaskScope.open()) {
            // 創建三個子任務
            var f1 = scope.fork(() -> simulatedIo("Service-A", 1200, false));
            var f2 = scope.fork(() -> simulatedIo("Service-B", 2500, true));
            var f3 = scope.fork(() -> simulatedIo("Service-C", 3000, false));

            // 等待所有子任務完成
            scope.join();

            // 處理結果
            System.out.println("Results: " +
                (f1.state() == StructuredTaskScope.Subtask.State.SUCCESS ? f1.get() : "Failed") + ", " +
                (f2.state() == StructuredTaskScope.Subtask.State.SUCCESS ? f2.get() : "Failed") + ", " +
                (f3.state() == StructuredTaskScope.Subtask.State.SUCCESS ? f3.get() : "Failed"));

        } catch (Exception e) {
            System.out.println("Scope finished with failure: " + e);
        }
    }

    /**
     * Compare Virtual Threads vs Native Threads with ASCII table output.
     */
    static void compareVirtualAndNativeThreads() throws Exception {
        int taskCount = 10_000;
        System.out.printf("Launching %,d simulated I/O tasks...\n", taskCount);

        long nativeTime = measureThreadTypePerformance(taskCount, false);
        long virtualTime = measureThreadTypePerformance(taskCount, true);

        long usedMem = getUsedMemoryMB();

        // ASCII Table output
        System.out.println();
        System.out.println("+--------------------+----------------+----------------+");
        System.out.println("| Thread Type        | Time (ms)      | Observations   |");
        System.out.println("+--------------------+----------------+----------------+");
        System.out.printf ("| %-18s | %-14d | %-14s |%n", "Native Threads", nativeTime, "limited pool");
        System.out.printf ("| %-18s | %-14d | %-14s |%n", "Virtual Threads", virtualTime, "scales easily");
        System.out.println("+--------------------+----------------+----------------+");
        System.out.printf ("| %-18s | %-14d | %-14s |%n", "Heap Used (MB)", usedMem, "after test");
        System.out.println("+--------------------+----------------+----------------+");
    }

    static long measureThreadTypePerformance(int count, boolean virtual) throws Exception {
        long start = System.currentTimeMillis();

        ExecutorService executor = virtual ? Executors.newVirtualThreadPerTaskExecutor()
                                           : Executors.newFixedThreadPool(200);

        try (executor) {
            for (int i = 0; i < count; i++) {
                int id = i;
                executor.submit(() -> {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException ignored) {}
                    return null;
                });
            }
        }

        long end = System.currentTimeMillis();
        return end - start;
    }

    static String simulatedIo(String name, long millis, boolean fail) throws InterruptedException {
        System.out.printf("[%s] running on %s, sleep %dms%n", name, Thread.currentThread(), millis);
        Thread.sleep(millis);
        if (fail) throw new RuntimeException(name + " failed!");
        return name + "-done";
    }

    static long getUsedMemoryMB() {
        MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
        long used = mbean.getHeapMemoryUsage().getUsed();
        return used / (1024 * 1024);
    }
}

編譯與執行

javac --enable-preview --release 25 StructuredConcurrencyVirtualThreadsExample.java
java --enable-preview StructuredConcurrencyVirtualThreadsExample

執行結果

Java: 25

=== Example 1: Structured Concurrency (ShutdownOnFailure) ===
[Service-A] running on VirtualThread[#26]/runnable@ForkJoinPool-1-worker-1, sleep 1200ms
[Service-C] running on VirtualThread[#30]/runnable@ForkJoinPool-1-worker-2, sleep 3000ms
[Service-B] running on VirtualThread[#28]/runnable@ForkJoinPool-1-worker-4, sleep 2500ms
Scope finished with failure: java.util.concurrent.StructuredTaskScope$FailedException: java.lang.RuntimeException: Service-B failed!

=== Example 2: Virtual vs Native Thread Performance Test ===
Launching 10,000 simulated I/O tasks...

+--------------------+----------------+----------------+
| Thread Type        | Time (ms)      | Observations   |
+--------------------+----------------+----------------+
| Native Threads     | 10232          | limited pool   |
| Virtual Threads    | 240            | scales easily  |
+--------------------+----------------+----------------+
| Heap Used (MB)     | 61             | after test     |
+--------------------+----------------+----------------+

同時支援舊的 Native Thread

  • 自動 join 或取消子任務;

  • 錯誤可統一處理;

  • 可以使用任意 Executor 來決定底層執行緒類型。

    static void example2() {
        try (var scope = StructuredTaskScope.open()) {

            // Virtual Thread Executor
            ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor();

            // fork 只能傳 Callable,不傳 Executor
            Callable<String> task1 = () -> simulatedIo("VT-A", 500, false);
            Callable<String> task2 = () -> simulatedIo("VT-B", 800, false);

            var f1 = scope.fork(task1);
            var f2 = scope.fork(task2);

            // Native Thread Executor
            ExecutorService nativeExecutor = Executors.newFixedThreadPool(2);
            Callable<String> cpuTask = () -> heavyComputation("NT-A");
            var f3 = scope.fork(cpuTask);

            scope.join(); // 等待所有子任務完成

            System.out.println("Results: " + f1.get() + ", " + f2.get() + ", " + f3.get());

            virtualExecutor.shutdown();
        } catch (Exception e) {
            System.out.println("Scope finished with failure: " + e);
        }

    }

    // 模擬 CPU 任務
    static String heavyComputation(String name) {
        System.out.printf("[%s] CPU task on %s%n", name, Thread.currentThread());
        long sum = 0;
        for (long i = 0; i < 10_000_000L; i++) sum += i;
        return name + "-done";
    }

2026/03/16

Java Virtual Thread

Java 從 Green Thread 時代開始,演進到使用 OS Thread,然後到了 Java 21 版,正式採用 Virtual Thread,以適應微服務時代伺服器的變革。

Green Threads

  • JDK 為了跨平台的特性,要讓程式可以在單核心,沒有多個 thread 的 OS 上運作,所以設計了內建的 thread 跟 scheduler

  • 在 JDK 1.1 (1997) 時期,Java 使用 green threads(由 JVM 在使用者空間模擬出來的執行緒,不直接用 OS thread)。

  • 優點:跨平台,不依賴作業系統執行緒,能讓多執行緒程式在沒有多執行緒支援的 OS 上跑。

  • 缺點:

    • JVM 本身要實作 thread scheduler,效能比不上 OS 提供的 thread。

    • 遇到 blocking I/O(例如 read socket),整個 JVM scheduler 都會被卡住,所有 green threads 都會停住。

  • 從 JDK 1.3 (2000) 開始,Java 全面切換到 1:1 OS threads 模型

Native Thread

作業系統核心會用 time-sharing 或 優先權排程 來管理 threads。

  • 重量級資源單位

    • 每個 OS thread 需要 stack (通常 1MB 預設)、TCB (thread control block)、kernel data structures。

    • 建立/銷毀成本高 (微秒到毫秒級)。

  • 數量有限

    • 即使硬體支援很多核心,實務上 JVM 或應用程式能開的 OS thread 數量大約只有幾千到幾萬。
  • blocking I/O 會卡住 thread

    • 假設某個 OS thread 正在 read() 一個 socket,整個 thread 會被 kernel block。

    • 即使這時 CPU 沒事幹,這個 thread 對 JVM 來說就是「卡住不能用」。

  • 搶佔式排程 (preemptive)

    • OS scheduler 會把 CPU 切給不同 threads。

    • context switch 成本高,要切換 CPU 狀態、register、stack。

  • 共享記憶體

    • 所有 thread 共用同一個 address space,必須透過鎖 (lock, monitor) 控制同步,容易出現 race condition、deadlock。
  • OS thread 模型對「大量 I/O 密集型應用」來說效率很差

    • 適合少量、CPU 密集的任務

    • 不適合製作非常大量的網路連線伺服器

Virtual Thread

  • Project Loom 在 Java 19 引入預覽功能、Java 21 變成正式 GA 的一個重要新功能
  • 輕量級:建立/銷毀成本非常低,幾 KB stack 就能跑。

  • blocking I/O 處理方式不同

    • Virtual Thread 呼叫阻塞 API (Socket.read()),JVM 會攔截並讓出 OS thread。

    • 這樣 OS thread 可以拿去執行其他 Virtual Thread,不會浪費資源。

  • 數量級提升

    • 可以開數百萬個 virtual threads,對應數百萬個並發請求。

    • 讓程式碼還是同步/直觀,但效能接近非同步 I/O 模型。

  • 簡化程式碼

    • 不需要複雜的 callback、CompletableFuture、reactive pipeline,直接用同步程式碼就能寫出 scalable 程式。
  • 把「blocking I/O」變得非阻塞化,同時又保留傳統同步 API 的簡單性,讓 Java 更適合現代微服務/高併發應用。

  • 虛擬線程更適合 I/O 型或高併發場景。如果是非常 CPU密集或如果內部有同步鎖 (synchronized) 或重度共享資源競爭,則使用傳統線程。

Erlang Process

  • 極度輕量:一個 process 只佔用幾 KB 記憶體,可以同時開數百萬個。由 BEAM VM 調度

  • 獨立記憶體空間:每個 process 有自己的 heap/stack,不共享狀態。

  • 排程由 BEAM 虛擬機控制:BEAM 用 OS threads(通常一個核心對應一個 scheduler thread)去執行上千萬個 Erlang processes。

  • preemptive scheduling:Erlang process 執行一定數量的 reductions(指令數)後會自動讓出 CPU。

  • Erlang process 的定位 比較接近 Java 的 Virtual Thread

    • 因為 Erlang 從設計之初就為了 massive concurrency,整個 I/O 模型與錯誤隔離都是以「百萬 process」為目標;

    • Java Virtual Thread 是在既有 thread-based API 上加的輕量執行緒,強調「低成本封裝既有同步程式碼」。

Erlang Process vs Java Threads 比較

特性 Erlang Process Java OS Thread Java Virtual Thread
管理單位 BEAM VM (使用 scheduler threads) OS Kernel JVM (基於 OS threads)
重量級 / 輕量級 超輕量 (幾 KB) 重量級 (MB) 輕量 (KB 級)
建立數量 百萬級 幾千~幾萬 百萬級
排程 BEAM VM preemptive scheduling OS scheduler JVM scheduler
blocking I/O 不會卡住整個 VM,process 掛起,scheduler 跑其他 process 卡住 OS thread 掛起虛擬執行緒,釋放 OS thread
共享記憶體 不共享,透過 message passing 共享,需要鎖 不共享(但可用同步物件)
錯誤隔離 完整隔離,崩潰不會影響其他 process 線程崩潰可能拖垮 JVM 崩潰只影響該 virtual thread
設計哲學 為 massive concurrency 與容錯而生 傳統 multi-threading 保留同步 API,實現高併發
適用場景 聊天室、遊戲伺服器(每個玩家一個 process)、即時推播(pub/sub 模式) I/O 密集、RPC、WebSocket CPU 密集、JNI、非阻塞演算法