Java執行緒的生命週期

Yang0710發表於2024-05-26

概要

目前CPU的運算速度已經達到了百億次每秒,甚至更高的量級,家用電腦即使維持作業系統正常執行的程式也會有數十個,執行緒更是數以百計。

執行緒是CPU的排程和分派的基本單位,為了更充分地利用CPU資源以及提高生產率和高效地完成任務,在現實場景中一般都會採用多執行緒處理。

執行緒的生命週期

執行緒的生命週期大致可以分為下面五種狀態:New(新建狀態)、RUNABLE(就緒狀態)、RUNNING(執行狀態)、休眠狀態DEAD(終止狀態)

image-20190331203604063

1、新建狀態,是執行緒被建立且未啟動的狀態;這裡的建立,僅僅是在JAVA的這種程式語言層面被建立,而在作業系統層面,真正的執行緒還沒有被建立。

Thread t1 = new Thread()
複製程式碼

2、就緒狀態,指的是呼叫start()方法之後,執行緒等待分配給CPU執行(這時候,執行緒已經在作業系統層面被建立)

t1.start()
複製程式碼

3、執行狀態,當CPU空閒時,執行緒被分得CPU時間片,執行Run()方法的狀態

4、休眠狀態,執行狀態的執行緒,如果呼叫一個阻塞的API或者等待某個事件,那麼執行緒的狀態就會轉換到休眠狀態,一般有以下幾種情況

  • 同步阻塞:鎖被其它執行緒佔用
  • 主動阻塞:呼叫Thread的某些方法,主動讓出CPU執行權,比如:sleep()、join()等方法
  • 等待阻塞:執行了wait()方法

5、終止狀態,執行緒執行完(run()方法執行結束)或者出現異常就會進入終止狀態

對應的是JAVA中Thread類State中的六種狀態

public class Thread implements Runnable {
   //.........
	
    public enum State {
        NEW, // 初始化狀態
      
        RUNNABLE, // 可執行/執行狀態
      
        BLOCKED, // 阻塞狀態
        
        WAITING, // 無時限等待
       
        TIMED_WAITING, // 有時限等待
  
        TERMINATED; // 終止狀態
    }
   
    // ..........
}
複製程式碼

休眠狀態(BLOCKEDWAITINGTIMED_WAITING)與RUNNING狀態的轉換

1、RUNNING狀態與BLOCKED狀態的轉換

  • 執行緒等待 synchronized 的隱式鎖,RUNNING —> BLOCKED

  • 執行緒獲得 synchronized 的隱式鎖,BLOCKED —> RUNNING

2、RUNNING狀態與WAITING狀態的轉換

  • 獲得 synchronized 隱式鎖的執行緒,呼叫無引數的Object.wait()方法
  • 呼叫無引數Thread.join()方法
  • 呼叫LockSupport.park()方法,執行緒阻塞切換到WAITING狀態,
  • 呼叫LockSupport.unpark()方法,可喚醒執行緒,從WAITING狀態切換到RUNNING狀態

3、RUNNING狀態與TIMED_WAITING狀態的轉換

  • 呼叫帶超時引數的 Thread..sleep(long millis)方法
  • 獲得 synchronized 隱式鎖的執行緒,呼叫帶超時引數的Object.wait(long timeout)方法
  • 呼叫帶超時引數的Thread.join(long millis)方法
  • 呼叫帶超時引數的LockSupport.parkNanos(Object blocker,long deadline)方法
  • 呼叫帶超時引數的LockSupport.parkUntil(long deadline)方法

廢棄掉的執行緒方法 :stop()、suspend()、resume()

stop()方法,會真正的殺死執行緒,不給執行緒任何喘息的機會,假設獲得 synchronized 隱式鎖的執行緒,此刻執行stop()方法,該鎖不會被釋放,導致其它執行緒沒有任何機會獲得鎖,顯然這樣的結果不是我們想要見到的。

suspend()resume()方法同樣,因為某種不可預料的原因,已經被建議不在使用

不能使用stop()、suspend() 、resume() 這些方法來終止執行緒或者喚醒執行緒,那麼我們應該使用什麼方法來做呢?答案是:優雅的使用Thread.interrupt()方法來做

優雅的Thread.interrupt()方法中斷執行緒

interrupt() 方法僅僅是通知執行緒,執行緒有機會執行一些後續操作,同時也可以無視這個通知,這個方法通過修改了呼叫執行緒的中斷狀態來告知那個執行緒,說它被中斷了,執行緒可以通過isInterrupted() 方法,檢測是不是自己被中斷。

當執行緒被阻塞的時候,比如被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞時;呼叫它的interrput()方法,會產生InterruptedException異常

擴充套件知識點

探祕區域性變數不會引發併發問題的原因

在Java領域,執行緒可以擁有自己的運算元棧,程式計數器、區域性變數表等資源;我們都知道,多個執行緒同時訪問共享變數的時候,會導致資料不一致性等併發問題;但是 Java 方法裡面的區域性變數是不存在併發問題的,具體為什麼呢?我們先來了解一下這些基礎知識。

區域性變數的作用域是方法內部的,當方法執行完了,區域性變數也就銷燬了,也就是說區域性變數應該是和方法同生共死的。

Java中的方法是如何呼叫的?當呼叫方法時,會建立新的棧幀,並壓入呼叫棧;當方法返回時,對應的棧幀就會被自動彈出。也就是說,棧幀和方法是同生共死的。

從上面我們可以得出:方法的呼叫就是壓棧和出棧的過程,而在Java中的方法的區域性變數又是儲存在棧幀中,所以我們用下面的示意圖幫助大家理解一下

image-20190330184914554

說了那麼多,我們還沒有解釋區域性變數為啥不會產生併發問題,以上,我們知道了,方法的呼叫是壓棧和出棧(棧幀)的過程,區域性變數又儲存在棧幀中。那麼我們的執行緒和呼叫棧又有什麼關係呢,答案是:每個執行緒都有自己獨立的呼叫棧

image-20190330190001478

到現在,相信大家都已經明白了,區域性變數之所以不存在併發問題,是因為,每個執行緒都有自己的呼叫棧,區域性變數都儲存線上程各自的呼叫棧裡面,沒有共享,自然就不存在併發問題。

歡迎大家關注公眾號:小白程式之路(whiteontheroad),第一時間獲取最新資訊!!!

小白程式之路

相關文章