Java 的多執行之管理執行緒,優先權隊列、鎖定資源 synchronized

Java 多執行緒

在實務中實作執行緒功能時,常需要管理這些產生的執行緒,如隊列與防止不同執行緒同時去存取到一個資源。本文章延續自以下幾篇文章:

執行緒的隊列(queue)

當執行緒的 sleep() 或 yeild() 方法被呼叫時,會暫停目前的工作,並進入執行緒的隊列(ready queue)排隊等待下一次 CPU 的執行單位。

使用 sleep( t ) 會暫停目前工作 t 毫秒,並進入隊列中等待執行,使用 yield() 方法則會讓出目前 CPU 的執行時間,直接進入隊列等待執行。

資源的鎖定

設計多執行緒程式時需考慮到避免多個執行緒存取同一個資源,例如不同執行緒同時間去開啟同一個檔案,Java 語言利用「同步方法 (method-level)」與「同步區塊 (block-level)」這兩個方式,提供開發人員使用,以避免此問題的發生。

同步方法

若為一個類別的方法加上 synchronized 修飾字,則會限定在同一時間,只能由此一個物件使用這個方法。假設有個繼承了 Thread 類別的魔法師 Wizard 類別,它擁有一個發出雷電的方法 thunder(),並持續兩秒鐘的時間,依照遊戲的規定,同一時間只能有一個魔法師使出雷電,假若目前場上有三個魔法師,Wizard 類別的設計如下:

03  public class Wizard extends Thread {
04      public void run() {
05          thunder();
06      }
07
08      public void thunder(){
09          System.out.println("THUNDER!!");
10          try {
11              sleep(2000);
12          } catch (InterruptedException e) {
13              e.printStackTrace();
14          }
15          System.out.println("END");
16      }
17
18      public static void main(String[] args) {
19          Wizard wizard = new Wizard();
20          Thread thr1 = new Thread(wizard);
21          thr1.start();
22          Thread thr2 = new Thread(wizard);
23          thr2.start();
24      }
25  } 

上述的 main 方法中產生兩個魔法師執行緒,並執行它們,在它們的 run() 方法中皆馬上呼叫 thunder() 方法,使用雷電絕招,執行結果如下:

THUNDER!!
THUNDER!!
END
END 

這樣的結果違背了遊戲的規定,同時間只能有一個執行緒使用 thunder(),要達到這個功能就需要同步方法(synchronized method),在 thunder 方法前加上「synchronized」修飾字,使其成為同步方法。同步方法被限定只能有一個物件單獨使用它,另一個執行緒就必需等待前一個執行完畢後才能取得執行權,thunder() 方法修改如下:

public synchronized void thunder() {
   ...(省略) 

再執行的結果,成功地限定同一時間只能讓一個執行緒執行雷電絕招:

THUNDER!!
END
THUNDER!!
END 

同步區塊

上述的同步方法可以達成大部份的需求,但是如果必需同步化的方法是由父類別繼承而來而無法更改,或是無法修改該方法為 synchronized 時,可以使用「同步區塊 (synchronized block)」,限定只能由單一執行緒能執行某段程式碼,與同步方法不同的是,同步區塊必需指定一個需要同步的物件,同步區塊的規格如下圖:

synchronized( 物件 ){
   程式碼
   程式碼
} 

將上一個魔法師類別稍作修改,成為 Wizard2 類別,同樣的遊戲規定,但取消 thunder()方法,而將其工作移至 run() 方法中,並為其加入同步區塊的設計:

03  public class Wizard2 extends Thread {
04      public void run() {
05          synchronized (this) {
06              System.out.println("THUNDER!!");
07              try {
08                  sleep(2000);
09              } catch (InterruptedException e) {
10                  e.printStackTrace();
11              }
12              System.out.println("END");
13          }
14      }
15
16      public static void main(String[] args) {
17          Wizard2 wizard = new Wizard2();
18          Thread thr1 = new Thread(wizard);
19          thr1.start();
20          Thread thr2 = new Thread(wizard);
21          thr2.start();
22      }
23  } 

Wizard2 類別的執行結果,可以達到與同步方法相同的效果:

THUNDER!!
END
THUNDER!!
END 

版權聲明

本文章授權範圍僅限綠豆湯網站使用,除  Facebook之類社群等未更改本文章出處之分享行為不在此限,其他個人或公司未經作者同意,不得任意將本文章內容轉載至其他網站,或以任何形式重製,為以免觸犯著作權法,請尊重作者之智慧財產權。

Comments

No comments yet. Why don’t you start the discussion?

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *