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、非阻塞演算法

2026/03/09

Shell Script 語法比較表

整理常用 Shell (sh, bash, zsh) 在語法、功能上的差異與相容性,方便參考。

類型 sh (POSIX) bash zsh 備註
Shebang #!/bin/sh #!/bin/bash #!/bin/zsh 建議腳本跨平台用 #!/bin/sh
變數宣告 name=value 相同 相同 不能有空格
字串插值 "Hello $name" 相同 相同 都支援
命令替換 `date`$(date) 相同 相同 建議用 $( )
條件判斷 [ "$a" = "$b" ] [ "$a" = "$b" ][[ $a == $b ]] [ "$a" = "$b" ][[ $a = $b ]] [[ ... ]] 不是 POSIX 的寫法
邏輯運算 [ "$a" = 1 ] && [ "$b" = 2 ] [[ $a = 1 && $b = 2 ]] 相同 [[ ... && ... ]] 非 POSIX
數學運算 $((1+2)) ((i++)) / $((1+2)) ((i++)) / $((1+2)) POSIX sh 只能 $(( ))
陣列 ❌ 不支援 arr=(a b c)${arr[0]} arr=(a b c)${arr[1]} Bash 陣列從 0 起算,Zsh 從 1 起算
關聯陣列 key=value declare -A map; map[key]=val typeset -A map; map[key]=val POSIX sh 沒有
brace expansion {1..5} {1..5} POSIX sh 不支援
迴圈 for for i in 1 2 3; do ...; done for i in {1..3}; do ...; done brace expansion 會展開整個序列 {1..3} 不是 POSIX
函數定義 foo() { ... } foo() { ... }function foo { ... } 相同 function foo {} 不是 POSIX
字串長度 ${#var} 相同 相同
字串比較 = == / = == / = (glob) POSIX sh 只能用 =
大小寫轉換 ${var^^} / ${var,,} ${(U)var} / ${(L)var} Bash/Zsh 特殊功能
字串切割 ${var%pattern} / ${var#pattern} 支援更多:${var^^} (大寫) 支援更多::${(U)var} POSIX 只有 % #
printf / echo printf 標準,echo 不一定支援 -e echo -e 可用 echo -e 可能無效,用 print 建議用 printf
測試檔案 [ -f file ] 相同 相同
正則比對 [[ string =~ regex ]] [[ string =~ regex ]] 但 regex 語法不同 POSIX sh 無 regex 功能
展開 (globbing) 基本 * ? [ ] shopt -s globstar Zsh 預設更強大 (e.g. **/*.txt) Zsh glob 功能最強
補全 (tab) bash-completion 內建強大補全 互動環境差異,不影響 script
錯誤處理 set -e 相同,加強版 set -o pipefail 相同 pipefail 不是 POSIX
信號處理 trap 'cmd' INT TERM 相同 相同
source 檔案 . file source file / . file source file / . file POSIX sh 用 .
目錄堆疊 pushd / popd pushd / popd POSIX sh 無目錄堆疊功能
互動功能 readline、history、completion history、completion、prompt customization Zsh 提供最強互動功能
local / typeset local / typeset local / typeset POSIX sh 不支援函數內局部變數

  • sh
    • POSIX,相容性最好,但功能有限。
    • 如果要製作跨平台的 script,就使用標準的 POSIX 語法
  • bash
    • 增強 POSIX,支援陣列、關聯陣列、[[ ]]、brace expansion、Bashisms。
    • 一般在 mac/linux,可使用 bash script
  • zsh
    • 幾乎包含 Bash 功能,互動功能更強(prompt、補全、glob、history)
    • 陣列索引從 1 開始,部分語法行為不同。
    • 使用者互動操作時,可使用 zsh