CodeGym /課程 /JAVA 25 SELF /多執行緒入門:為什麼需要

多執行緒入門:為什麼需要

JAVA 25 SELF
等級 51 , 課堂 0
開放

1. 什麼是執行緒(thread)

執行緒作為獨立的工作線

在 Java(以及程式設計整體)中,執行緒是彼此獨立的指令序列,能與同一程式中的其他執行緒並行運作。想像一間工廠:每位裁縫都有自己的工作桌與任務,各自獨立工作,但成果匯聚成共同的結果。

預設情況下,Java 程式從單一執行緒開始——也就是啟動 main 方法的那個。不過,我們可以建立額外的執行緒,讓程式的不同部分同時執行。

行程與執行緒:有何差別?

  • 行程——「重量級」的執行單位。每個行程有自己的記憶體區域、變數與資源。行程彼此完全隔離——就算其中一個「故障」,其他也不會受影響。
  • 執行緒(thread)——行程內的「輕量級」執行單位。同一行程的所有執行緒共享記憶體與資源。這表示它們能輕鬆交換資料(但也可能彼此干擾)。

類比:
行程就像一間獨立的公寓:有自己的牆與住戶。
執行緒就像同一間公寓裡的住戶:各做各的,但共用廚房和浴室。

在 Java 中如何呈現?

當你啟動程式時,JVM 會至少建立一個執行緒——主執行緒(main)。但你可以建立新的執行緒,讓任務平行執行。

2. 為什麼需要多執行緒

介面回應性:UI 不應該「卡住」

假設你在寫一個圖形介面程式——比如文字編輯器。使用者按下「儲存」,而你開始長時間把檔案寫到磁碟。如果一切都在主執行緒中進行,視窗就會「卡住」:使用者無法點選、游標不動、介面沒有反應。如果把儲存動作放到獨立執行緒——介面仍保持回應,使用者甚至可以反悔並關閉程式。

生活化範例:
你打開瀏覽器開始下載大檔。如果瀏覽器不使用執行緒,你在檔案完成下載前,既不能開新分頁,也不能捲動頁面!

平行處理資料

例如,你有一份包含上千個檔案的清單需要處理(像是重新計算雜湊或替換文字)。為何不平行處理?每個執行緒拿一個檔案、各自獨立處理,整體工作就能快上好幾倍。

範例:
伺服器需要處理數百位客戶端的請求。若伺服器只用單一執行緒,其他客戶端可能要等到天荒地老。使用執行緒時,每個請求能被獨立處理!

善用多核心處理器

現代處理器不是只有一顆「大腦」,而是一整隊核心,可同時工作。如果你的程式只使用一個執行緒,其餘核心就會閒著,彷彿在玩踩地雷。啟動多個執行緒——所有核心都有事做,程式執行得更快。

有趣的小知識:
你的手機都有多個核心,筆電與伺服器甚至有數十個!不用滿它們,就好比買了公車卻一個人搭。

3. 生活中的例子

領域 多執行緒範例
檔案下載 同時下載多個檔案
使用者介面(UI) 載入/儲存資料時應用程式不會「卡住」
伺服器 平行處理大量網路請求
遊戲 物理、圖形、音訊、AI 各自使用獨立執行緒
即時通訊 接收訊息、傳送檔案、更新介面
影像處理 逐畫面平行處理

小小比喻:
廚師在煮湯、烤箱同時在烤派、掃地機器人在打掃——一切同時進行,晚餐更快就緒!

4. 多執行緒的潛在複雜性

競爭條件(race condition)
當多個執行緒同時修改同一變數時,結果可能不可預期。比如兩個執行緒同時遞增共享計數器,最終值可能不正確。之後的課程我們會更詳細討論。

同步
為了避免執行緒彼此干擾,必須想辦法「協調」——誰在何時可以修改資料。這稱為同步。為此有特殊的關鍵字與結構(synchronized、鎖等),我們稍後會介紹。

Deadlock(互相鎖死)
有時執行緒會彼此「等待」到天荒地老,導致程式卡死。這稱為 deadlock——是多執行緒程式設計中最陰險的錯誤之一。

除錯與測試
多執行緒的錯誤很難捕捉:有時正常、有時不正常。有些錯誤只在伺服器或使用者端出現,而在你的電腦上卻完美無缺。這讓多執行緒程式的測試與除錯成為開發者的真正任務關卡。

5. 快速總覽:多執行緒程式長什麼樣

沒有執行緒的範例:

public class Main {
    public static void main(String[] args) {
        // 數到 5
        for (int i = 1; i <= 5; i++) {
            System.out.println(i);
        }
        // 輸出字母
        for (char c = 'A'; c <= 'E'; c++) {
            System.out.println(c);
        }
    }
}

輸出總是一樣:

1
2
3
4
5
A
B
C
D
E

使用執行緒的範例:

public class Main {
    public static void main(String[] args) {
        Thread numbers = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println(i);
                try {
                    Thread.sleep(100); // 稍微等待一下
                } catch (InterruptedException e) {
                    // 忽略
                }
            }
        });

        Thread letters = new Thread(() -> {
            for (char c = 'A'; c <= 'E'; c++) {
                System.out.println(c);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // 忽略
                }
            }
        });

        numbers.start();
        letters.start();
    }
}

輸出會交錯:

1
A
2
B
3
C
4
D
5
E

或者,如果執行緒「競賽」,順序可能會不同。重點是——兩個迴圈是並行進行的!

6. 實用細節

視覺示意:執行緒如何一起工作

+-------------------+     +-------------------+
|   Glavnyy potok   |     |    Vtoroy potok   |
+-------------------+     +-------------------+
| 1 | 2 | 3 | 4 | 5 |     | A | B | C | D | E |
+-------------------+     +-------------------+
        |                         |
        |      Oba rabotayut      |
        |      odnovremenno       |
        +-------------------------+

Java 在哪些地方「底層」使用了執行緒

  • 垃圾回收(Garbage Collector)——獨立的執行緒清理未使用的物件。
  • 輸入/輸出(I/O)——檔案讀寫、網路連線。
  • 伺服器與 Web 應用——每個客戶端請求在獨立執行緒中處理。
  • 計時器、排程器——依排程執行任務。

7. 新手常見錯誤

錯誤 1:以為執行緒一定會加速程式。
事實上,如果你的機器是單核心,或工作安排不當,多執行緒反而可能因為「混亂」與執行緒切換的額外開銷而變慢。

錯誤 2:忽視同步問題。
很多人以為:「我就只是啟兩個執行緒,能出什麼事?」但如果兩個執行緒都在修改同一個變數,結果可能完全出乎意料。

錯誤 3:凡事都丟給執行緒。
不要為每個小需求都啟一個獨立執行緒。執行緒是一種資源,數量過多會導致變慢,甚至讓程式崩潰。

錯誤 4:沒有處理錯誤。
執行緒可能會拋出例外(例如處理檔案或網路時)。如果不處理這些錯誤,程式可能會異常終止或「卡住」。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION