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


線程生命周期5種狀態

介紹

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

狀態轉換圖

在這里插入圖片描述

新建(New)

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

就緒(Runnable)

  也稱為“可執行狀態”,線程對象調用start()方法后,該線程處於就緒狀態。如thread.start()。Java虛擬機會為其創建方法調用棧和程序計數器(線程私有),處於就緒狀態的線程並沒有開始運行,只是表示該線程可以運行,線程何時運行取決於JVM中線程調度器的調度。

運行(Running)

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

阻塞(Boloked)

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

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

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

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

死亡(Dead)

以如下3種方式結束線程

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

注意:

  • 當拋出一個異常后程序會結束,所以線程會終止;
  • sleep()方法會阻塞一個線程並不會終止;
  • 創建一個新的線程也不會終止另一個線程。

判斷線程是否死亡

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

start()和run()方法詳解

start()和run()介紹

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

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()方法控制並發編程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM