Java—線程的生命週期及線程控制方法詳解

線程生命週期5種狀態

介紹

  線程的生命週期通過新建(New)、就緒(Runnable)、運行(Running)、阻塞(Bolocked)和死亡(Dead)java

狀態轉換圖

在這裏插入圖片描述

新建(New)

  程序使用new關鍵字建立一個線程以後,該線程就處於新建狀態,僅僅由Java虛擬機爲其分配內存,並初始化其成員變量的值不會執行線程的線程執行體。如Thread thread = new Thread()編程

就緒(Runnable)

  也稱爲「可執行狀態」,線程對象調用start()方法後,該線程處於就緒狀態。如thread.start()。Java虛擬機會爲其建立方法調用棧和程序計數器(線程私有),處於就緒狀態的線程並無開始運行,只是表示該線程能夠運行,線程什麼時候運行取決於JVM中線程調度器的調度。多線程

運行(Running)

  處於就緒狀態的線程得到CPU,開始執行run()方法的線程執行體,則該線程處於運行狀態。(注意:線程只能從就緒狀態進入到運行狀態)併發

阻塞(Boloked)

  阻塞狀態是線程由於某種緣由放棄了CPU的使用權,暫時中止運行,直到線程進入就緒狀態,纔有機會轉到運行狀態。當調用sleep()、一個阻塞式IO方法、同步鎖、等待通知、suspend()方法掛起都會使線程進入阻塞狀態。ide

  • 線程調用sleep()方法主動放棄所佔用的處理器資源;
  • 線程調用一個阻塞式IO方法,在該方法返回以前,該線程被阻塞;
  • 線程試圖得到一個同步監視器,但該同步監視器正被其餘線程所持有;
  • 線程在等待(wait())某個通知(notify());
  • 程序調用了線程的suspend()方法將該線程掛起,但這個方法易形成死鎖,應該避免使用。

  線程從阻塞狀態解除——進入就緒狀態的過程:函數

  • 調用sleep()方法的線程通過了指定時間
  • 線程調用的阻塞式IO方法已經返回
  • 線程成功地得到試圖取得的同步監視器(鎖)
  • 線程正在等待某個通知時,其餘線程發出了一個通知
  • 處於掛起狀態的線程被調用了resume()恢復方法。

死亡(Dead)

以以下3種方式結束線程

  • run()call()方法執行完成,線程正常結束;
  • 線程拋出一個未捕獲的ExceptionError
  • 直接調用該線程的stop()方法來結束該線程(該方法易形成死鎖,不推薦使用)

注意:this

  • 當拋出一個異常後程序會結束,因此線程會終止;
  • sleep()方法會阻塞一個線程並不會終止;
  • 建立一個新的線程也不會終止另外一個線程。

判斷線程是否死亡

  能夠經過isAlive()方法,線程對象的isAlive()方法返回true,即爲線程存活;返回false,即爲線程死亡。
  線程處於就緒、運行、阻塞狀態時,isAlive()返回true;線程處於新建、死亡狀態時,isAlive()返回false線程

start()和run()方法詳解

start()和run()介紹

  當程序使用new關鍵字建立了一個線程後,該線程就處於新建狀態,此時它和其餘Java對象是同樣的,只是由JVM爲其分配內存,並初始化其成員變量的值(此時線程對象沒有任何的行爲,也不執行線程執行體)。
  當線程對象調用了start()方法後,線程就處於就緒狀態,JVM爲其建立方法調用棧和程序計數器,處於這個狀態中的線程尚未真正的開始運行,只是表示這個線程此時是一個可運行狀態。什麼時候能運行?取決於JVM的線程調度器的調度。
  處於就緒狀態的線程獲取CPU執行權限,開始執行run()方法的線程執行體,此時線程處於運行狀態。(若只有一個CPU,任什麼時候刻只能有一個線程處於運行狀態,多線程處理任務時,會給人一種併發錯覺,實際是CPU執行速度較快,多線程交織執行任務而已)3d

start()方法源碼

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        //若線程不是就緒狀態,就拋出異常
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        //將線程添加到ThreadGroup中
        group.add(this);

        boolean started = false;
        try {
        	//經過start0()方法啓動線程
            start0();
            //設置線程啓動的started標誌位
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

  start()實際上經過本地方法start0()啓動線程,會新運行一個線程,新線程會調用run()方法。日誌

run()方法源碼

@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

  targetRunnable對象run()直接調用Thread線程Runnable成員run()方法,並不會新建一個線程。

線程控制方法

sleep()方法

sleep()方法介紹

  1. sleep(long millis)方法是Thread類的一個靜態方法,做用是讓當前線程暫停一段時間,並進入阻塞狀態。

sleep()方法重載方式

  • public static native void sleep(long millis) throws InterruptedException:讓當前正在執行的線程暫停millis毫秒,並進入阻塞狀態。
  • public static void sleep(long millis, int nanos) throws InterruptedException:讓當前正在執行的線程暫停millis毫秒+nanos毫微秒,並進入阻塞狀態。(不多用)

sleep()示例

一般用法就是

//讓當前線程睡眠1000毫秒,即暫定1s
Thread.sleep(1000);

yield()方法

yield()方法介紹

  1. yield()方法讓當前正在執行的線程暫停,但不會阻塞線程,只是讓線程轉入就緒狀態。
  2. yield()方法讓當前線程暫停,讓系統的線程調度從新調度一次,因此會出現當某個線程調用了yield()方法後,線程調度器又從新將它調度出來執行。
  3. yield()方法讓當前線程暫停後,只有優先級>=當前線程的處於就緒狀態的線程才能獲取CPU執行權限。

yield()方法重載

  • public static native void yield();:靜態方法。

yield()示例

//讓當前線程暫停
Thread.yield();

線程優先級

  1. 每一個線程執行都有必定的優先級,優先級高的線程得到CPU執行權限的機會比較大。
  2. 每一個線程默認的優先級與建立它的父線程的優先級相同。因此main線程的優先級通常和本身建立的子線程優先級同樣。
  3. Thread類提供setPriority(int newPriority)getPriority()方法設置和返回指定線程的優先級。其中setPriority()方法的參數能夠是一個整數(1-10之間),也能夠是靜態常量。
    MAX_PRIORITY:值爲10.
    MIN_PRIORITY:值爲1.
    NORM_PRIORITY:值爲5.

join()方法

join()方法介紹

  1. Thread類提供join()方法讓一個線程等待另外一個線程完成的方法;就是將指定的線程加入到當前線程,這樣兩個交替執行的線程就變成了順序執行的線程,如線程A調用了線程B的join()方法,則線程A會等待線程B執行完畢後纔會繼續執行本身。
  2. join()方法由使用線程的程序調用,調用線程調用線程t的t.join()方法後將會被阻塞,直到線程t執行完畢,調用線程才能繼續執行。通常就是用於主線程內,等待其餘線程執行完畢後,再繼續執行main()函數。

join()方法重載方式

  • public final void join() throws InterruptedException:等待被join的線程執行完畢。
  • public final synchronized void join(long millis) throws InterruptedException:等待被join的線程的超時時間爲millis毫秒。若是在millis毫秒內被join的線程還未結束執行流程,則調用線程再也不等待。
  • public final synchronized void join(long millis, int nanos) throws InterruptedException:等待被join的線程的時間最長爲millis毫秒+nanos毫微秒。(不多用)

join()方法示例

(1)未使用join()方法

代碼

public class JoinMethodTest {

    public static void main(String[] args) {
        System.out.println("main thread start");

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("child thread start");
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("child thread finshed");
            }
        });
        thread.start();
        System.out.println("main thread finshed");
    }
}

運行結果

main thread start
main thread finshed
child thread start
child thread finshed

  能夠從運行結果看出,main()主線程日誌打印的很快,沒有等待子線程打印就結束了。

(2)使用join()方法

代碼

public class JoinMethodTest {

    public static void main(String[] args) {
        System.out.println("main thread start");

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("child thread start");
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("child thread finshed");
            }
        });
        thread.start();
        //加入join()方法等待子線程執行完畢,才執行主線程。
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main thread finshed");
    }
}

運行結果

main thread start
child thread start
child thread finshed
main thread finshed

  從運行結果能夠看出,main thread finshed結果是在最後打印的,加入join()方法等待子線程執行完畢,才執行主線程。

6種狀態的線程生命週期解釋

在這裏插入圖片描述

Q&A

爲什麼啓動線程須要用start()方法而不是直接調用run()方法?

  1. 調用start()方法啓動線程,系統會將該線程對象的run()方法看成線程執行體來處理。
  2. 直接調用線程對象的run()方法,該方法會被當即執行,而在run()方法返回以前其餘線程沒法併發執行(系統會將線程對象的看成一個普通對象,將run()方法看成一個普通方法,而不是線程執行體。)

start()方法和run()方法

java Thread中,run方法和start()方法的區別
  • 概念start()是啓動線程,讓線程重新建狀態變爲就緒狀態;線程獲得CPU時間片後,執行run()中的線程執行體;
  • 調用次數start()只能調用一次;run()能夠重複調用。
  • 方法類型:啓動線程只能用start(),系統會把run()方法當作線程執行體處理;若是直接調用run(),系統會把線程對象看成普通對象,此時run()也是一個普通方法,而不是線程執行體。run()方法只是類的一個普通方法而已,若是直接調用run方法,程序中依然只有主線程這一個線程,其程序執行路徑仍是隻有一條,仍是要順序執行,仍是要等待run方法體執行完畢後纔可繼續執行下面的代碼。。
  • 源碼start()源碼中實際上經過本地方法start0()啓動線程,會新運行一個線程,新線程會調用run()方法;而run()源碼中targetRunnable對象run()直接調用Thread線程Runnable成員的run()方法,並不會新建一個線程。
  • 多線程:用 start方法來啓動線程,是真正實現了多線程, 經過調用Thread類的start()方法來啓動一個線程,這時此線程處於就緒(可運行)狀態,並無運行,一旦獲得cpu時間片,就開始執行run()方法。但要注意的是,此時無需等待run()方法執行完畢,便可繼續執行下面的代碼。因此run()方法並無實現多線程。

sleep()和yield()方法的區別

  1. 依賴線程優先級:sleep()方法暫停當前線程後,會給其餘線程執行機會,而不在意其餘線程的優先級;
    yield()方法暫停當前線程後,只會給優先級相同或更高的線程執行機會。
  2. 線程轉入狀態:sleep()方法將線程轉入阻塞狀態,知道通過阻塞時間纔會轉入就緒狀態;
    yield()方法不會將線程轉入阻塞狀態,而是將線程轉入就緒狀態。
  3. 異常聲明:sleep()方法聲明拋出了InterruptedException異常;
    yield()方法未聲明拋出異常。
  4. 可移植性: sleep()方法的移植性比yield()方法好,因此通常使用sleep()方法控制併發編程。
相關文章
相關標籤/搜索