Java 多執行緒編程


Java給了多執行緒程式設計內建的支援。一個多執行緒程式包含兩個或多個能並發運行的部分。程式的每一部分都稱作一個線程,並且每個線程定義了一個獨立的執行路徑。

多執行緒是多任務的一種特別的形式,但多執行緒使用了更小的資源開銷。

這裡定義和執行緒相關的另一個術語 - 行程:一個行程包括由作業系統分配的記憶體空間,包含一個或多個執行緒。一個執行緒不能獨立的存在,它必須是行程的一部分。一個行程一直運行,直到所有的非守候執行緒都結束運行後才能結束。

多執行緒能滿足程式設計師編寫高效率的程式來達到充分利用CPU的目的。


一個執行緒的生命週

執行緒經過其生命週期的各個階段。下圖顯示了一個線程完整的生命週期。

java-thread.jpg

  • 新狀態:

    #使用new 關鍵字與 Thread 類別或其子類別建立一個執行緒物件後,該執行緒物件就處於新建狀態。它保持這個狀態直到程式 start() 這個線程。

  • 就緒狀態:

    當執行緒物件呼叫了start()方法之後,該執行緒就進入就緒狀態。就緒狀態的執行緒處於就緒佇列中,要等待JVM裡執行緒調度器的調度。

  • 運行狀態:

    如果就緒狀態的執行緒取得CPU 資源,就可以執行run(),此時執行緒便處於運作狀態。處於運作狀態的執行緒最為複雜,它可以變成阻塞狀態、就緒狀態和死亡狀態。

  • 阻塞狀態:

    如果一個執行緒執行了sleep(睡眠)、suspend(掛起)等方法,失去所佔用資源之後,該執行緒就從運行狀態進入阻塞狀態。在睡眠時間已到或獲得設備資源後可以重新進入就緒狀態。

  • 死亡狀態:

    一個運行狀態的執行緒完成任務或其他終止條件發生時,執行緒就切換到終止狀態。


執行緒的優先權

每一個Java執行緒都有一個優先權,這有助於作業系統決定執行緒的排程順序。

Java執行緒的優先權是一個整數,其取值範圍是1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

預設情況下,每個執行緒都會指派一個優先權NORM_PRIORITY(5)。

具有較高優先權的執行緒對程式更重要,並且應該在低優先權的執行緒之前分配處理器資源。但是,執行緒優先權不能保證執行緒執行的順序,而且非常依賴平台。


建立一個執行緒

Java提供了兩種建立執行緒方法:

  • #透過實作Runable介面;

  • #透過繼承Thread類別本身。


透過實作Runnable介面來建立線程

建立一個線程,最簡單的方法是建立一個實作Runnable介面的類別。

為了實作Runnable,一個類別只需要執行一個方法呼叫run(),宣告如下:


public void run()

你可以重寫該方法,重要的是理解的run()可以呼叫其他方法,使用其他類,並聲明變量,就像主線程一樣。

在建立一個實作Runnable介面的類別之後,你可以在類別中實例化一個執行緒物件。

Thread定義了幾個建構方法,下面的這個是我們常用的:

Thread(Runnable threadOb,String threadName);

這裡,threadOb 是實作Runnable 介面的類別的實例,並且threadName指定新執行緒的名字。

新執行緒建立之後,你呼叫它的start()方法它才會運作。

void start();

實例

下面是一個建立執行緒並開始讓它執行的實例:

// 创建一个新的线程
class NewThread implements Runnable {
   Thread t;
   NewThread() {
      // 创建第二个新线程
      t = new Thread(this, "Demo Thread");
      System.out.println("Child thread: " + t);
      t.start(); // 开始线程
   }
  
   // 第二个线程入口
   public void run() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Child Thread: " + i);
            // 暂停线程
            Thread.sleep(50);
         }
     } catch (InterruptedException e) {
         System.out.println("Child interrupted.");
     }
     System.out.println("Exiting child thread.");
   }
}
 
public class ThreadDemo {
   public static void main(String args[]) {
      new NewThread(); // 创建一个新线程
      try {
         for(int i = 5; i > 0; i--) {
           System.out.println("Main Thread: " + i);
           Thread.sleep(100);
         }
      } catch (InterruptedException e) {
         System.out.println("Main thread interrupted.");
      }
      System.out.println("Main thread exiting.");
   }
}

編譯以上程式執行結果如下:

Child thread: Thread[Demo Thread,5,main]
Main Thread: 5
Child Thread: 5
Child Thread: 4
Main Thread: 4
Child Thread: 3
Child Thread: 2
Main Thread: 3
Child Thread: 1
Exiting child thread.
Main Thread: 2
Main Thread: 1
Main thread exiting.

#透過繼承Thread來建立執行緒

建立一個執行緒的第二種方法是建立一個新的類,該類別繼承Thread類,然後建立一個該類別的實例。

繼承類別必須重寫run()方法,該方法是新執行緒的入口點。它也必須呼叫start()方法才能執行。

實例

// 通过继承 Thread 创建线程
class NewThread extends Thread {
   NewThread() {
      // 创建第二个新线程
      super("Demo Thread");
      System.out.println("Child thread: " + this);
      start(); // 开始线程
   }
 
   // 第二个线程入口
   public void run() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Child Thread: " + i);
                            // 让线程休眠一会
            Thread.sleep(50);
         }
      } catch (InterruptedException e) {
         System.out.println("Child interrupted.");
      }
      System.out.println("Exiting child thread.");
   }
}
 
public class ExtendThread {
   public static void main(String args[]) {
      new NewThread(); // 创建一个新线程
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Main Thread: " + i);
            Thread.sleep(100);
         }
      } catch (InterruptedException e) {
         System.out.println("Main thread interrupted.");
      }
      System.out.println("Main thread exiting.");
   }
}

編譯以上程式執行結果如下:

Child thread: Thread[Demo Thread,5,main]
Main Thread: 5
Child Thread: 5
Child Thread: 4
Main Thread: 4
Child Thread: 3
Child Thread: 2
Main Thread: 3
Child Thread: 1
Exiting child thread.
Main Thread: 2
Main Thread: 1
Main thread exiting.

Thread 方法

下表列出了Thread類別的一些重要方法:

##                    1                    2                    3                    4                    5                    6                    7                    8

測試執行緒是否處於活動狀態。 上述方法是被Thread物件呼叫的。下面的方法是Thread類別的靜態方法。

序號方法說明
public void start()# 使該執行緒開始執行;
Java 虛擬機器呼叫該執行緒的 run 方法。
public void run()# 如果該執行緒是使用獨立的 Runnable 來執行物件建構的,則呼叫該 Runnable 物件的 run 方法;否則,該方法不執行任何操作並傳回。
public final void setName(String name) 改變線程名稱,使其與參數 name 相同。
public final void setPriority(int priority)  更改執行緒的優先權。
public final void setDaemon(boolean on)# 將該線程標記為守護線程或使用者線程。
public final void join(long millisec)# 等待該執行緒終止的時間最長為 millis 毫秒。
public void interrupt() 中斷線程。
public final boolean isAlive() 測試線程是否處於活動狀態。
##                    1                    2                    3                    4                    5
序號# 說明
public static void yield() 暫停目前正在執行的線程對象,並執行其他線程。
public static void sleep(long millisec)# 在指定的毫秒數內讓目前正在執行的執行緒休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。
public static boolean holdsLock(Object x)# 當且僅噹噹前執行緒在指定的物件上保持監視器鎖定時,才傳回 true。
public static Thread currentThread()# 傳回對目前正在執行的線程物件的參考。
public static void dumpStack() 將目前執行緒的堆疊追蹤列印至標準錯誤流。
實例

如下的ThreadClassDemo 程式示範了Thread類別的一些方法:

// 文件名 : DisplayMessage.java
// 通过实现 Runnable 接口创建线程
public class DisplayMessage implements Runnable
{
   private String message;
   public DisplayMessage(String message)
   {
      this.message = message;
   }
   public void run()
   {
      while(true)
      {
         System.out.println(message);
      }
   }
}
// 文件名 : GuessANumber.java
// 通过继承 Thread 类创建线程

public class GuessANumber extends Thread
{
   private int number;
   public GuessANumber(int number)
   {
      this.number = number;
   }
   public void run()
   {
      int counter = 0;
      int guess = 0;
      do
      {
          guess = (int) (Math.random() * 100 + 1);
          System.out.println(this.getName()
                       + " guesses " + guess);
          counter++;
      }while(guess != number);
      System.out.println("** Correct! " + this.getName()
                       + " in " + counter + " guesses.**");
   }
}
// 文件名 : ThreadClassDemo.java
public class ThreadClassDemo
{
   public static void main(String [] args)
   {
      Runnable hello = new DisplayMessage("Hello");
      Thread thread1 = new Thread(hello);
      thread1.setDaemon(true);
      thread1.setName("hello");
      System.out.println("Starting hello thread...");
      thread1.start();
     
      Runnable bye = new DisplayMessage("Goodbye");
      Thread thread2 = new Thread(bye);
      thread2.setPriority(Thread.MIN_PRIORITY);
      thread2.setDaemon(true);
      System.out.println("Starting goodbye thread...");
      thread2.start();
 
      System.out.println("Starting thread3...");
      Thread thread3 = new GuessANumber(27);
      thread3.start();
      try
      {
         thread3.join();
      }catch(InterruptedException e)
      {
         System.out.println("Thread interrupted.");
      }
      System.out.println("Starting thread4...");
      Thread thread4 = new GuessANumber(75);
     
           thread4.start();
      System.out.println("main() is ending...");
   }
}

執行結果如下,每一次執行的結果都不一樣。

Starting hello thread...
Starting goodbye thread...
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Thread-2 guesses 27
Hello
** Correct! Thread-2 in 102 guesses.**
Hello
Starting thread4...
Hello
Hello
..........remaining result produced.

執行緒的幾個主要概念:

在多執行緒程式設計時,你需要了解以下幾個概念:

  • 執行緒同步

  • 執行緒間通訊

  • #執行緒死鎖

  • 執行緒控制:掛起、停止和復原


多執行緒的使用

#有效利用多執行緒的關鍵是理解程式是並發執行而不是串列執行的。例如:程式中有兩個子系統需要並發執行,這時候就需要利用多執行緒程式設計。

透過對多執行緒的使用,可以寫出非常有效率的程式。不過請注意,如果你創建太多的線程,程式執行的效率實際上是降低了,而不是提升了。

請記住,上下文的切換開銷也很重要,如果你創建了太多的線程,CPU花費在上下文的切換的時間將多於執行程式的時間!