java併發編程(三)java線程狀態與方法 一、線程的狀態 二、Thread的常用方法 三、方法與狀態轉換

一、線程的狀態

1.1 操作系統層面

在操作系統層面有五種狀態:

  • 【初始狀態】僅是在語言層面創建了線程對象,還未與操作系統線程關聯
  • 【可運行狀態】(就緒狀態)指該線程已經被創建(與操作系統線程關聯),可以由 CPU 調度執行
  • 【運行狀態】指獲取了 CPU 時間片運行中的狀態。當 CPU 時間片用完,會從【運行狀態】轉換至【可運行狀態】,會導致線程的上下文切換
  • 【阻塞狀態】
    • 如果調用了阻塞 API,如 BIO 讀寫文件,這時該線程實際不會用到 CPU,會導致線程上下文切換,進入【阻塞狀態】
    • 等 BIO 操作完畢,會由操作系統喚醒阻塞的線程,轉換至【可運行狀態】
    • 與【可運行狀態】的區別是,對【阻塞狀態】的線程來說只要它們一直不喚醒,調度器就一直不會考慮調度它們
  • 【終止狀態】表示線程已經執行完畢,生命週期已經結束,不會再轉換爲其它狀態

1.2 Java的Thread狀態

Thread的狀態,是一個enum,有六種狀態,如下所示:

public enum State {
    /**
     * 初始
     */
    NEW,
    /**
     * 可運行
     */
    RUNNABLE,
    /**
     * 阻塞
     */
    BLOCKED,
    /**
     * 等待
     */
    WAITING,
    /**
     * 超時等待
     */
    TIMED_WAITING,
    /**
     * 終止
     */
    TERMINATED;
}
  • NEW 線程剛被創建,但是還沒有調用 start() 方法
  • RUNNABLE 當調用了 start() 方法之後,注意,Java API 層面的 RUNNABLE 狀態涵蓋了 操作系統 層面的【可運行狀態】、【運行狀態】和【阻塞狀態】(由於 BIO 導致的線程阻塞,在 Java 裏無法區分,仍然認爲是可運行)
  • BLOCKED , WAITING , TIMED_WAITING 都是 Java API 層面對【阻塞狀態】的細分,在後面第三節(方法與狀態轉換)會講解
  • TERMINATED 當線程代碼運行結束

二、Thread的常用方法

2.1 常用方法

方法名 static 功能說明 注意
start() 啓動一個線程,線程當中運行run()方法中的代碼 start 方法只是讓線程進入就緒,裏面代碼不一定立刻運行(CPU 的時間片還沒分給它)。每個線程對象的start方法只能調用一次,如果調用了多次會出現IllegalThreadStateException
run() 線程啓動後調用的方法 如果在構造 Thread 對象時傳遞了 Runnable 參數,則線程啓動後會調用 Runnable 中的 run 方法,否則默認不執行任何操作。但可以創建 Thread 的子類對象,來覆蓋默認行爲;

class ExtendThread extends Thread {
   @Override
   public void run() {
    System.out.println("繼承Thread類方式");
   }
}
join() 等待當前線程執行結束 在當前執行線程a中,另一個線程b調用該方法,則線程a進入WAITING狀態,直到線程b執行完畢,線程a繼續執行
join(long n) 等待當前線程運行結束,最多等待 n毫秒
getId() 獲取線程長整型的 id 唯一id
getName() 獲取線程名稱
setName(String) 修改線程名稱
getPriority() 獲取線程優先級
setPriority(int) 修改線程優先級 java中規定線程優先級是1~10 的整數,較大的優先級能提高該線程被 CPU 調度的機率
getState() 獲取線程狀態 Java 中線程狀態是用 6 個 enum 表示,分別爲:
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
isInterrupted() 判斷是否被打斷 不會清除 打斷標記
isAlive() 判斷線程是否存活(是否運行完畢)
interrupt() 打斷線程 如果被打斷線程正在 sleep,wait,join 會導致被打斷的線程拋出InterruptedException,並清除 打斷標記 ;

如果打斷的正在運行的線程,則會設置 打斷標記 ;

park 的線程被打斷,也會設置 打斷標記。
interrupted() static 判斷當前線程是否被打斷 會清除 打斷標記
currentThread() static 獲取當前正在執行的線程
sleep(long n) static 讓當前執行的線程休眠n毫秒,休眠時讓出 cpu的時間片給其它線程
yied() static 提示線程調度器讓出當前線程對CPU的使用 主要是爲了測試和調試

2.2 sleep和yied

2.2.1 sleep

  1. 調用 sleep 會讓當前線程從 Running 進入 Timed Waiting 狀態(阻塞)
  2. 其它線程可以使用 interrupt 方法打斷正在睡眠的線程,這時 sleep 方法會拋出 InterruptedException
  3. 睡眠結束後的線程未必會立刻得到執行
  4. 建議用 TimeUnit 的 sleep 代替 Thread 的 sleep 來獲得更好的可讀性(內部也是Thread.sleep)
TimeUnit.SECONDS.sleep(5);

2.2.2 yied

  1. 調用 yield 會讓當前線程從 Running 進入 Runnable 就緒狀態,然後調度執行其它線程
  2. 具體的實現依賴於操作系統的任務調度器

2.3 interrupt 方法詳解

線程的Thread.interrupt()方法是中斷線程,將會設置該線程的中斷狀態,即設置爲true。

其作用僅僅而已,線程關閉還是繼續執行業務進程應該由我們的程序自己進行判斷。

針對不同狀態下的線程進行中斷操作,會有不一樣的結果:

2.3.1 中斷wait() 、join()、sleep()

如果此線程在調用Object類的wait() 、 wait(long)或wait(long, int)方法或join() 、 join(long) 、 join(long, int)被阻塞、 sleep(long)或sleep(long, int) ,此類的方法,則其中斷狀態將被清除並收到InterruptedException 。

以sleep舉例:

    public static void main(String[] args) {

        Thread t = new Thread(() -> {
            System.out.println("打斷狀態:" + Thread.currentThread().isInterrupted());
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("打斷後的狀態:" + Thread.currentThread().isInterrupted());
        });
        t.start();
        t.interrupt();
        System.out.println("打斷狀態:" + t.isInterrupted());
    }

結果:

打斷狀態:true
打斷狀態:true
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at com.cloud.bssp.thread.InterruptTest.lambda$main$0(InterruptTest.java:18)
    at java.lang.Thread.run(Thread.java:748)

2.3.2 中斷正常線程

正常線程將會被設置中斷標記位,我們可以根據該標記位判斷線程如何執行,如下所示:

    /**
     * 中斷正常線程
     *
     * @param args
     */
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (true){
                if(Thread.currentThread().isInterrupted()){
                    System.out.println("中斷狀態:" + Thread.currentThread().isInterrupted());
                    break;
                }
            }
        });
        t.start();
        t.interrupt();
    }

結果:

中斷狀態:true

2.3.3 中斷park線程

不會使中斷狀態清除。

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("park");
                LockSupport.park();
                System.out.println("unpark");
                System.out.println("中斷狀態:" + Thread.currentThread().isInterrupted());
                if (Thread.currentThread().isInterrupted()) {
                    break;
                }
            }
        });
        t.start();
        TimeUnit.SECONDS.sleep(1);
        t.interrupt();
    }

結果:

park
unpark
中斷狀態:true

如果在park之前,線程已經是中斷狀態了,則會使park失效,如下所示,除了首次park成功能成功,被中斷後,後面的park都失效了:

   /**
     * 中斷park
     *
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("打斷狀態:" + Thread.currentThread().isInterrupted());
                System.out.println("park..." + i);
                LockSupport.park();
            }
        });
        t1.start();
        TimeUnit.SECONDS.sleep(1);
        t1.interrupt();
    }

結果:

打斷狀態:false
park...0
打斷狀態:true
park...1
打斷狀態:true
park...2
打斷狀態:true
park...3
打斷狀態:true
park...4

可以 Thread.interrupted() 方法去除中斷標記:

2.3.5 不推薦使用的方法

方法名稱 描述
stop() 停止線程運行。不安全的,並將會在未來版本刪除
suspend() 掛起(暫停)線程運行,此方法已被棄用,因爲它本質上容易死鎖
resume() 恢復線程運行。此方法僅用於suspend ,已被棄用,因爲它容易死鎖

2.3.4 其他中斷

  • 如果此線程在InterruptibleChannel的 I/O 操作中被阻塞,則通道將關閉,線程的中斷狀態將被設置,並且線程將收到java.nio.channels.ClosedByInterruptException 。

  • 如果該線程在java.nio.channels.Selector被阻塞,則該線程的中斷狀態將被設置,並且它將立即從選擇操作中返回,可能具有非零值,就像調用了選擇器的wakeup方法。

三、方法與狀態轉換

如下圖所示,線的右側表示執行的方法:

下面具體分析方法和狀態轉換,假設有一個線程Thread t:

1.NEW --> RUNNABLE

執行t.start()

2.RUNNABLE <--> WAITING

此種狀態轉換分三種情況:
1)t 線程用 synchronized(obj) 獲取了對象鎖後,調用 obj.wait() 方法時,t 線程從 RUNNABLE --> WAITING

調用 obj.notify() , obj.notifyAll() , t.interrupt() 時:

  • 競爭鎖成功,t 線程從 WAITING --> RUNNABLE
  • 競爭鎖失敗,t 線程從 WAITING --> BLOCKED

2)當前線程調用 t.join() 方法時,當前線程從 RUNNABLE --> WAITING

注意是當前線程在t 線程對象的監視器上等待

當前線程會等到t執行結束後或調用了當前線程的 interrupt() 時,WAITING --> RUNNABLE。

3)當前線程調用 LockSupport.park() 方法會讓當前線程從 RUNNABLE --> WAITING

調用 LockSupport.unpark(目標線程) 或調用了線程 的 interrupt() ,會讓目標線程從 WAITING --> RUNNABLE

3.RUNNABLE <--> TIMED_WAITING

此種狀態轉換分四種情況:

1) t 線程用 synchronized(obj) 獲取了對象鎖後,調用 obj.wait(long n) 方法時,t 線程從 RUNNABLE --> TIMED_WAITING

t 線程等待時間超過了 n 毫秒,或調用 obj.notify() , obj.notifyAll() , t.interrupt() 時:

  • 競爭鎖成功,t 線程從 TIMED_WAITING --> RUNNABLE
  • 競爭鎖失敗,t 線程從 TIMED_WAITING --> BLOCKED

2)當前線程調用 t.join(long n) 方法時,當前線程從 RUNNABLE --> TIMED_WAITING

注意是當前線程在t 線程對象的監視器上等待

當前線程等待時間超過了 n 毫秒,或t 線程運行結束,或調用了當前線程的 interrupt() 時,當前線程從TIMED_WAITING --> RUNNABLE

3)當前線程調用 Thread.sleep(long n) ,當前線程從 RUNNABLE --> TIMED_WAITING

當前線程等待時間超過了 n 毫秒,當前線程從 TIMED_WAITING --> RUNNABLE

4)當前線程調用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 時,當前線程從 RUNNABLE --> TIMED_WAITING

調用 LockSupport.unpark(目標線程) 或調用了線程 的 interrupt() ,或是等待超時,會讓目標線程從
TIMED_WAITING--> RUNNABLE

4.RUNNABLE <--> BLOCKED

t 線程用 synchronized(obj) 獲取了對象鎖時如果競爭失敗,從 RUNNABLE --> BLOCKED

持 obj 鎖線程的同步代碼塊執行完畢,會喚醒該對象上所有 BLOCKED 的線程重新競爭,如果其中 t 線程競爭成功,從 BLOCKED --> RUNNABLE ,其它失敗的線程仍然 BLOCKED

5.RUNNABLE <--> TERMINATED

當前線程所有代碼運行完畢,進入 TERMINATED

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章