在踏入 Java 並發編程的大門前,必須先釐清「並發」與「並行」這兩個核心概念,它們不僅是作業系統的基石,更是高效編寫多線程程式的關鍵認知。
傳統的順序編程模型中,程式在任意時刻只能執行單一步驟,如同單行道般有序但效率受限。而並發的目標是在單一處理器上,透過時間片輪轉,在同一時間段內邏輯上同時處理多個任務。想像一位廚師獨自在廚房備菜、炒菜、洗碗,透過快速切換任務,讓多項工作看似同步進行。
與並發不同,並行發生在多核處理器環境下,多個任務在物理時間上真正同時執行。這就像廚房裡有多位廚師,各自負責不同菜餚,真正實現了同步作業。並發著重於單核效率最大化,並行則著重於多核吞吐量提升。
Java 語言原生支援多線程,這是其區別於 C/C++ 等傳統語言的重要特性。對於 Web 開發者而言,Servlet 容器(如 Tomcat)本質上就是多線程伺服器,每個 HTTP 請求都由獨立線程處理;Swing/JavaFX 等 GUI 框架也依賴事件分發線程(EDT)確保介面響應。若不掌握並發精髓,極易陷入死鎖、資料競爭、記憶體可見性等難以除錯的陷阱。
進程是執行中程式的實例,擁有獨立的虛擬位址空間、檔案描述符、訊號處理器等資源,由 PCB(Process Control Block)管理。作業系統透過進程切換實現多任務,進程間天然隔離,安全性高,但創建、銷毀與上下文切換開銷極大(需切換頁表、刷新 TLB 等),受限於系統資源,不適合高併發場景。
線程隸屬於進程,共享進程的記憶體空間(堆、方法區)與檔案句柄,僅擁有私有的程式計數器、虛擬機棧、本地方法棧。線程創建銷毀快、切換開銷小(無需切換地址空間),是 CPU 調度的基本單位。Java 並發模型即建立在線程之上,透過共享記憶體實現高效協作,但也引入了同步與一致性的複雜性——這正是並發編程藝術的核心挑戰。
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("執行緒任務執行中...");
}
}
// 使用
new MyThread().start();
簡單直觀,但受限於 Java 單繼承,擴展性差。start() 啟動新線程並自動呼叫 run(),直接呼叫 run() 只是普通方法調用,不會開啟新線程。
public class Task implements Runnable {
@Override
public void run() {
// 任務邏輯
}
}
// 使用
new Thread(new Task()).start();
將「任務」與「執行者」解耦,符合職責單一原則。同一 Runnable 實例可提交給多個線程或線程池複用,靈活性大增。Java 8 後更可配合 Lambda 表達式簡化寫法:new Thread(() -> System.out.println("Hello")).start();。
Callabletask = () -> { TimeUnit.SECONDS.sleep(2); return 42; }; FutureTask futureTask = new FutureTask<>(task); new Thread(futureTask).start(); Integer result = futureTask.get(); // 阻塞獲取結果
適用於需要異步計算結果的場景,Future 提供取消、超時控制等進階功能。
Java 線程擁有 6 種狀態:NEW(新建) → RUNNABLE(可執行) → BLOCKED/WAITING/TIMED_WAITING(阻塞/等待/計時等待) → TERMINATED(終止)。核心方法包括:
start():啟動線程,進入就緒狀態sleep()/yield():靜態方法,讓出 CPU 時間片join():等待目標線程終止interrupt():中斷線程(協作式中斷)wait()/notify()/notifyAll():Object 監視器方法,需在 synchronized 區塊中使用理解進程與線程的本質差異、掌握線程創建與控制的基本 API,是並發編程的入門磚。但真正的挑戰在於:如何安全地共享可變狀態?如何設計無鎖演算法?如何利用 java.util.concurrent 套件中的高階組件(線程池、佇列、同步器、Concurrent 集合)構建高效穩定的系統?後續文章將深入探討記憶體模型、鎖優化、並發集合、線程池原理與實戰調優,敬請期待。