CodeGym /課程 /JAVA 25 SELF /執行緒的參數與優先順序

執行緒的參數與優先順序

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

1. 將參數傳遞給執行緒

當你啟動執行緒時,常常會想要傳給它一些「工作」:檔名、數字範圍或問候語。沒有參數的執行緒就像沒有地址的外送員:在城市裡跑來跑去,卻不知道該把披薩送到哪裡。

要怎麼做?

在 Java 中,建立執行緒通常有兩種方式:

  • 繼承 Thread 類別
  • 實作 Runnable 介面(或使用 lambda 運算式)

最常見的作法是透過建構子傳遞參數(類別需實作 Runnable)。這樣簡單、安全且易懂。將欄位宣告為 final,你就能保護參數不被其他執行緒修改。使用 setter/公開欄位容易引發同步問題。

範例 1:透過建構子傳遞參數的執行緒

來改造一個教學小程式:在獨立執行緒中處理使用者的訂單。

public class OrderProcessor implements Runnable {
    private final String orderId;

    public OrderProcessor(String orderId) {
        this.orderId = orderId;
    }

    @Override
    public void run() {
        System.out.println("正在處理訂單: " + orderId + " 在執行緒 " + Thread.currentThread().getName());
        // 這裡可能會有耗時的處理...
    }
}

建立並啟動執行緒:

public class Main {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new OrderProcessor("ORDER-001"));
        Thread thread2 = new Thread(new OrderProcessor("ORDER-002"));
        thread1.start();
        thread2.start();
    }
}

預期輸出:

正在處理訂單: ORDER-001 在執行緒 Thread-0
正在處理訂單: ORDER-002 在執行緒 Thread-1

範例 2:透過 lambda 運算式傳入參數的執行緒(Java 8+)

若任務很簡單,可以不用另外定義類別。

public class Main {
    public static void main(String[] args) {
        String user1 = "Vasya";
        String user2 = "Katya";

        Thread thread1 = new Thread(() -> {
            System.out.println("你好, " + user1 + " 來自 " + Thread.currentThread().getName());
        });

        Thread thread2 = new Thread(() -> {
            System.out.println("你好, " + user2 + " 來自 " + Thread.currentThread().getName());
        });

        thread1.start();
        thread2.start();
    }
}

為什麼不應該用 setter/getter 來傳遞參數?
如果你透過 setter 或公開欄位來傳遞參數,其他執行緒可能在執行期間直接改變其值,導致難以追蹤的錯誤。最好將欄位宣告為 final,並透過建構子傳遞。

2. 執行緒的優先順序

在 Java 中,每個執行緒都有一個優先順序 — 介於 110 的整數,用來告訴排程器(scheduler)這個執行緒相對於其他執行緒「有多重要」。預設所有執行緒的優先順序是 5Thread.NORM_PRIORITY)。

重要:優先順序只是給作業系統的「提示」。它不能保證優先順序為 10 的執行緒一定會比優先順序為 1 的執行緒跑得更快。一切取決於作業系統、其設定與當前負載。

如何設定執行緒的優先順序?

Thread 類別提供以下方法:

  • setPriority(int newPriority)
  • getPriority()

以及三個標準常數:

  • Thread.MIN_PRIORITY1
  • Thread.NORM_PRIORITY5
  • Thread.MAX_PRIORITY10

範例 3:設定執行緒的優先順序

public class PriorityDemo {
    public static void main(String[] args) {
        Runnable task = () -> {
            System.out.println("執行緒 " + Thread.currentThread().getName() +
                    " 的優先順序為 " + Thread.currentThread().getPriority());
        };

        Thread low = new Thread(task, "LowPriority");
        Thread norm = new Thread(task, "NormalPriority");
        Thread high = new Thread(task, "HighPriority");

        low.setPriority(Thread.MIN_PRIORITY);    // 1
        norm.setPriority(Thread.NORM_PRIORITY);  // 5
        high.setPriority(Thread.MAX_PRIORITY);   // 10

        low.start();
        norm.start();
        high.start();
    }
}

預期輸出(行的順序不保證!):

執行緒 LowPriority 的優先順序為 1
執行緒 HighPriority 的優先順序為 10
執行緒 NormalPriority 的優先順序為 5

優先順序會影響執行順序嗎?

多數情況下 — 不會。優先順序可能會影響某個執行緒得到的 CPU 時間長短,但不能保證啟動或結束的先後順序。不要把程式邏輯建立在優先順序上 — 把它們當作「柔性」提示使用,例如讓背景執行緒別干擾主要執行緒。

表格:執行緒優先順序常數

常數 說明
Thread.MIN_PRIORITY
1 最低優先順序
Thread.NORM_PRIORITY
5 一般優先順序
Thread.MAX_PRIORITY
10 最高優先順序

3. 為執行緒命名

執行緒名稱是多工世界中的「暱稱」。當你有數十個執行緒時,除錯更容易:在日誌裡看到 "Thread-7" 不如看到 "FileUploader-1"。這在分析日誌與尋找錯誤時尤其重要。

如何替執行緒命名?

可以在 Thread 建構子中直接指定名稱,或使用 setName 方法設定。取得目前名稱 — getName

Thread t = new Thread(() -> {
    System.out.println("我在工作!");
}, "MyThreadName");
t.start();

範例 4:具名執行緒

public class NamedThreads {
    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            System.out.println("我是執行緒: " + Thread.currentThread().getName());
        }, "Downloader");

        Thread threadB = new Thread(() -> {
            System.out.println("我是執行緒: " + Thread.currentThread().getName());
        });
        threadB.setName("Uploader");

        threadA.start();
        threadB.start();
    }
}

預期輸出:

我是執行緒: Downloader
我是執行緒: Uploader

4. 實作:具有不同參數、優先順序與名稱的多個執行緒

把一切整合在一個範例中:每張訂單都是一個獨立的執行緒,具有自己的名稱與優先順序。

public class OrderProcessor implements Runnable {
    private final String orderId;
    private final int processingTimeMs;

    public OrderProcessor(String orderId, int processingTimeMs) {
        this.orderId = orderId;
        this.processingTimeMs = processingTimeMs;
    }

    @Override
    public void run() {
        System.out.println("[" + Thread.currentThread().getName() + "] 開始處理訂單 " + orderId +
                " (優先順序 " + Thread.currentThread().getPriority() + ")");
        try {
            Thread.sleep(processingTimeMs); // 模擬工作
        } catch (InterruptedException e) {
            System.out.println("[" + Thread.currentThread().getName() + "] 已中斷!");
        }
        System.out.println("[" + Thread.currentThread().getName() + "] 完成處理訂單 " + orderId);
    }

    public static void main(String[] args) {
        Thread fastOrder = new Thread(new OrderProcessor("FAST-ORDER", 500), "FastOrderThread");
        Thread normalOrder = new Thread(new OrderProcessor("NORMAL-ORDER", 1000), "NormalOrderThread");
        Thread slowOrder = new Thread(new OrderProcessor("SLOW-ORDER", 2000), "SlowOrderThread");

        fastOrder.setPriority(Thread.MAX_PRIORITY);
        normalOrder.setPriority(Thread.NORM_PRIORITY);
        slowOrder.setPriority(Thread.MIN_PRIORITY);

        fastOrder.start();
        normalOrder.start();
        slowOrder.start();
    }
}

會看到:

  • 每個執行緒都會輸出它正在處理哪張訂單、自己的名稱以及優先順序。
  • 透過 Thread.sleep 模擬處理時間。
  • 執行緒的完成順序可能與優先順序不一致。

5. 重要細節與注意事項

參數傳遞:只透過建構子!

這樣較為安全。將參數宣告為 final — 物件建立後就無法再被修改,其他執行緒也無法「塞」入自己的值。這對避免資料競爭至關重要。

優先順序:不要把商業邏輯建立在它上面

優先順序就像對服務生說:「可以幫我快一點端咖啡嗎?」有時有效、有時不一定。它是給作業系統的建議,而非執行規則。

執行緒名稱:用於除錯

在分析日誌時能大幅節省時間。養成一開始就設定有意義名稱的習慣。

6. 處理執行緒參數與優先順序時的常見錯誤

錯誤 1:透過公用欄位傳遞參數。 若把參數宣告為一般欄位並在執行緒啟動後修改,可能導致不可預期的結果。請使用 final 欄位與建構子。

錯誤 2:以為優先順序能保證執行順序。Thread.MAX_PRIORITY 設到最高,就以為該執行緒永遠第一?不。不要用優先順序來做同步或控制邏輯。

錯誤 3:日誌中沒有命名的執行緒。 當日誌裡只有「Thread-3」「Thread-7」時,很難找出問題來源。請用建構子或 setName 為執行緒設定有意義的名稱。

錯誤 4:多個執行緒共用同一個 Runnable 物件。 如果 Runnable 物件持有狀態,而你把它傳給多個執行緒,它們就會「共享」參數 — 直通資料競爭。為每個執行緒建立獨立的 Runnable

錯誤 5:透過 set 方法傳遞參數。 如果在建立/啟動執行緒後才用 set* 設定參數,數值可能會在執行途中被改變 — 典型的 race condition

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