1. 程式人生 > 程式設計 >Java 控制執行緒的方法

Java 控制執行緒的方法

Java 的執行緒支援提供了一些便捷的工具方法,通過這些便捷的工具方法可以很好地控制執行緒的執行。

join 執行緒

Thread 提供了讓一個執行緒等待另一個執行緒完成的方法—— join() 方法。當在某個程式執行流中呼叫其他執行緒的 join() 方法時,呼叫執行緒將被阻塞,直到被 join() 方法加入的 join 執行緒執行完為止。

join() 方法通常由使用執行緒的程式呼叫,以將大問題劃分成許多小問題,每個小問題分配一個執行緒。當所有的小問題都得到處理後,再呼叫主執行緒來進一步操作。

public class JoinThread extends Thread{
  // 提供一個有引數的構造器,用於設定該執行緒的名字
  public JoinThread(String name) {
    super(name);
  }
  //重寫run()方法,定義執行緒執行體
  public void run() {
    for (int i=0 ; i < 100; i++) {
      System.out.println(this.getName() + " " + i);
    }
  }
  public static void main(String[] args) throws Exception {
    //啟動子執行緒
    new JoinThread("新執行緒").start();
    for(int i=0 ; i < 100; i++) {
      if(i==20) {
        JoinThread jt = new JoinThread("被Join的執行緒");
        jt.start();
        // main執行緒呼叫了 jt 執行緒的 join() 方法
        // main執行緒必須等jt執行結束後才會向下執行
        jt.join();
      }
      System.out.println(Thread.currentThread().getName() + " " + i);
    }
  }
}

上面程式中一共有3個執行緒,主方法開始時就啟動了名為“新執行緒”的子執行緒,該子執行緒將會和 main 執行緒併發執行。當主執行緒的迴圈變數 i 等於20時,啟動了名為“被 Join 的執行緒”的執行緒,該執行緒不會和 main 執行緒併發執行 , main 執行緒必須等該執行緒執行結束後才可以向下執行。在名為“被 Join 的執行緒”的執行緒執行時,實際上只有2個子執行緒併發執行,而主執行緒處於等待狀態。執行上面程式,會看到如下圖所示的執行效果。

Java 控制執行緒的方法

主執行緒執行到 i == 20時,程式啟動並 join 了名為“被 Join 的執行緒”的執行緒,所以主執行緒將一直處於阻塞狀態,直到名為“被 Join 的執行緒”的執行緒執行完成。

join() 方法有如下三種過載形式:

  • join():等待被 join 的執行緒執行完成。
  • join(long millis):等待被 join 的執行緒的時間最長為 millis 毫秒。如果在 millis 毫秒內被 join 的執行緒還沒有執行結束,則不再等待。
  • join(long millis,int nanos):等待被 join 的執行緒的時間最長為 millis 毫秒加 nanos 毫微秒。

提示:通常很少使用第三種形式,原因有兩個:程式對時間的精度無須精確到毫微秒;計算機硬體、作業系統本身也無法精確到毫微秒。

後臺執行緒

有一種執行緒,它是在後臺執行的,它的任務是為其他的執行緒提供服務,這種執行緒被稱為“後臺執行緒(Daemon Thread)”,又稱為“守護執行緒”或“精靈執行緒”。 JVM 的垃圾回收執行緒就是典型的後臺執行緒。

後臺執行緒有個特徵:如果所有的前臺執行緒都死亡,後臺執行緒會自動死亡。

呼叫 Thread 物件的 setDaemon(true) 方法可將指定執行緒設定成後臺執行緒。下面程式將執行執行緒設定成後臺執行緒,可以看到當所有的前臺執行緒死亡時,後臺執行緒隨之死亡。當整個虛擬機器中只剩下後臺執行緒時,程式就沒有繼續執行的必要了,所以虛擬機器也就退出了。

public class DaemonThread extends Thread {
  // 定義後臺執行緒的執行緒執行體與普通執行緒沒有任何區別
  public void run() {
    for (int i=0 ; i < 1000; i++) {
      System.out.println(this.getName() + " " + i);
    }
  }
  
  public static void main(String[] args) {
    DaemonThread t = new DaemonThread();
    // 將此執行緒設定為後臺執行緒
    t.setDaemon(true);
    // 啟動後臺執行緒
    t.start();
    for (int i=0; i < 100; i++) {
      System.out.println(Thread.currentThread().getName() + " " + i);
    }
    // ---------程式執行到此處,前臺執行緒(main執行緒)結束----------
    // 後臺執行緒也應該隨之結束      
  }
}

上面程式中的粗體字程式碼先將t執行緒設定成後臺執行緒,然後啟動該執行緒,本來該執行緒應該執行到 i 等於999時才會結束,但執行程式時不難發現該後臺執行緒無法執行到999,因為當主執行緒也就是程式中唯一的前臺執行緒執行結束後,JVM 會主動退出,因而後臺執行緒也就被結束了。

Thread 類還提供了一個 isDaemon() 方法,用於判斷指定執行緒是否為後臺執行緒。

從上面程式可以看出,主執行緒預設是前臺執行緒,t執行緒預設也是前臺執行緒。並不是所有的執行緒預設都是前臺執行緒,有些執行緒預設就是後臺執行緒——前臺執行緒建立的子執行緒預設是前臺執行緒,後臺執行緒建立的子執行緒預設是後臺執行緒。

注意:前臺執行緒死亡後, JVM 會通知後臺執行緒死亡,但從它接收指令到做出響應,需要一定時間。而且要將某個執行緒設定為後臺執行緒,必須在該執行緒啟動之前設定,也就是說,setDaemon(true) 必須在 start() 方法之前呼叫,否則會引發 IllegalThreadStateException異常。

執行緒睡眠:sleep

如果需要讓當前正在執行的執行緒暫停一段時間,並進入阻塞狀態,則可以通過呼叫 Thread 類的靜態 sleep() 方法來實現。sleep() 方法有兩種過載形式。

  • static void sleep(long millis):讓當前正在執行的執行緒暫停millis毫秒,並進入阻塞狀態,該方法受到系統計時器和執行緒排程器的精度與準確度的影響。
  • static void sleep(long millis,int nanos):讓當前正在執行的執行緒暫停 millis 毫秒加 nanos 毫微秒,並進入阻塞狀態,該方法受到系統計時器和執行緒排程器的精度與準確度的影響。

與前面類似的是,程式很少呼叫第二種形式的 sleep() 方法。

噹噹前執行緒呼叫 sleep() 方法進入阻塞狀態後,在其睡眠時間段內,該執行緒不會獲得執行的機會,即使系統中沒有其他可執行的執行緒,處於 sleep() 中的執行緒也不會執行,因此 sleep() 方法常用來暫停程式的執行。

下面程式呼叫 sleep() 方法來暫停主執行緒的執行,因為該程式只有一個主執行緒,當主執行緒進入睡眠後,系統沒有可執行的執行緒,所以可以看到程式在 sleep() 方法處暫停。

public class SleepTest {
  public static void main(String[] args) throws Exception {
    for(int i=0;i<10;i++) {
      System.out.println("當前時間:"+new Date());
      // 呼叫sleep() 方法讓當前執行緒暫停1s
      Thread.sleep(1000);
    }
  }
}

上面程式中的粗體字程式碼將當前執行的執行緒暫停 1 秒,執行上面程式,看到程式依次輸出10條字串,輸出2條字串之間的時間間隔為1秒。

執行緒讓步: yield

yield() 方法是一個和 sleep() 方法有點相似的方法,它也是 Thread 類提供的一個靜態方法,它也可以讓當前正在執行的執行緒暫停,但它不會阻塞該執行緒,它只是將該執行緒轉入就緒狀態。 yield() 只是讓當前執行緒暫停一下,讓系統的執行緒排程器重新排程一次,完全可能的情況是:當某個執行緒呼叫了 yield() 方法暫停之後,執行緒排程器又將其排程出來重新執行。

實際上,當某個執行緒呼叫了 yield() 方法暫停之後,只有優先順序與當前執行緒相同,或者優先順序比當前執行緒更高的處於就緒狀態的執行緒才會獲得執行的機會。下面程式使用 yield() 方法來讓當前正在執行的執行緒暫停。

public class YieldTest extends Thread{
  public YieldTest(String name) {
    super(name);
  }
  // 定義run()方法作為執行緒執行體
  public void run() {
    for(int i=0;i<50;i++) {
      System.out.println(getName()+"  "+i);
      // 當 i等於20時,使用 yield() 方法讓當前執行緒讓步
      if(i==20) {
        Thread.yield();
      }
    }
  }
  public static void main(String[] args) {
    // 啟動兩個併發執行緒 
    YieldTest yt1 = new YieldTest("高階");
    // 將yt1執行緒設定成最高優先順序
    // yt1.setPriority(Thread.MAX_PRIORITY);
    yt1.start();
    YieldTest yt2 = new YieldTest("低階");
    // 將yt2執行緒設定成最低優先順序
    // yt2.setPriority(Thread.MIN_PRIORITY);
    yt2.start();
  }
}

注意:在多 CPU 並行的環境下, yield() 方法的功能有時候並不明顯。

關於 sleep() 方法和 yield() 方法的區別如下。

  • sleep() 方法暫停當前執行緒後,會給其他執行緒執行機會,不會理會其他執行緒的優先順序;但 yield() 方法只會給優先順序相同,或優先順序更高的執行緒執行機會。
  • sleep() 方法會將執行緒轉入阻塞狀態,直到經過阻塞時間才會轉入就緒狀態;而 yield() 不會將執行緒轉入阻塞狀態,它只是強制當前執行緒進入就緒狀態。因此完全有可能某個執行緒呼叫 yield() 方法暫停之後,立即再次獲得處理器資源被執行。
  • sleep() 方法宣告丟擲了 InterruptedException 異常,所以呼叫 sleep() 方法時要麼捕捉該異常,要麼顯式宣告丟擲該異常;而 yield ()方法則沒有宣告丟擲任何異常。
  • sleep() 方法比 yield() 方法有更好的可移植性,通常不建議使用 yield() 方法來控制併發執行緒的執行。

改變執行緒優先順序

每個執行緒執行時都具有一定的優先順序,優先順序高的執行緒獲得較多的執行機會,而優先順序低的執行緒則獲得較少的執行機會。

每個執行緒預設的優先順序都與建立它的父執行緒的優先順序相同,在預設情況下, main 執行緒具有普通優先順序,由 main 執行緒建立的子執行緒也具有普通優先順序。

Thread 類提供了 setPriority(int newPriority)、 getPriority() 方法來設定和返回指定執行緒的優先順序,其中 setPriority() 方法的引數可以是一個整數,範圍是1~10之間,也可以使用 Thread 類的如下三個靜態常量。

  • MAX _ PRIORITY:其值是 10。
  • MIN _ PRIORITY:其值是 1 。
  • NORM _ PRIORITY:其值是 5。

下面程式使用了 setPriority() 方法來改變主執行緒的優先順序,並使用該方法改變了兩個執行緒的優先順序,從而可以看到高優先順序的執行緒將會獲得更多的執行機會。

public class PriorityTest extends Thread{
  public PriorityTest(String name) {
    super(name);
  }
  public void run() {
    for(int i=0;i<50;i++) {
      System.out.println(getName()+",其優先順序是:"+getPriority()+",迴圈變數的值為:"+i);
    }
  }
  public static void main(String[] args) {
    // 改變主執行緒的優先順序
    Thread.currentThread().setPriority(6);
    for(int i=0;i<30;i++) {
      if(i==10) {
        PriorityTest low = new PriorityTest("低階");
        low.start();
        System.out.println("建立之初的優先順序:"+low.getPriority());
        // 設定該執行緒為最低優先順序
        low.setPriority(Thread.MIN_PRIORITY);
      }
      if(i==20) {
        PriorityTest high = new PriorityTest("高階");
        high.start();
        System.out.println("建立之初的優先順序:"+high.getPriority());
        // 設定該執行緒為最高優先順序
        high.setPriority(Thread.MAX_PRIORITY);
      }
    }
  }
}

上面程式中的第一行粗體字程式碼改變了主執行緒的優先順序為6,這樣由main執行緒所建立的子執行緒的優先順序預設都是6,所以程式直接輸出 low、high 兩個執行緒的優先順序時應該看到6。接著程式將 low 執行緒的優先順序設為 Priority.MIN_PRIORITY,將 high 執行緒的優先順序設定為 Priority.MAX_PRIORITY。

執行上面程式,會看到如下圖所示的效果。

Java 控制執行緒的方法

值得指出的是,雖然 Java 提供了 10 個優先順序級別,但這些優先順序級別需要作業系統的支援。遺憾的是,不同作業系統上的優先順序並不相同,而且也不能很好地和 Java 的10個優先順序對應,例如 Windows 2000 僅提供了 7個優先順序。因此應該儘量避免直接為執行緒指定優先順序,而應該使用 MAX_PRIORITY、MIN_PRIORITY 和 NORM_PRIORITY 三個靜態常量來設定優先順序,這樣才可以保證程式具有最好的可移植性。

以上就是Java 控制執行緒的方法的詳細內容,更多關於Java 控制執行緒的資料請關注我們其它相關文章!