亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java線程的6種狀態(tài)及轉(zhuǎn)化方式

 更新時間:2024年09月19日 10:48:51   作者:養(yǎng)歌  
本文詳細(xì)介紹了Java線程的六種狀態(tài)以及狀態(tài)之間的轉(zhuǎn)換關(guān)系,線程狀態(tài)包括NEW(新建)、RUNNABLE(運行)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(超時等待)和TERMINATED(終止)

前言

線程是 JVM 執(zhí)行任務(wù)的最小單元,理解線程的狀態(tài)轉(zhuǎn)換是理解后續(xù)多線程問題的基礎(chǔ)。

當(dāng)我們說一個線程的狀態(tài)時,其實說的就是一個變量的值,在 Thread 類中的一個變量,叫 private volatile int threadStatus = 0;

這個值是個整數(shù),不方便理解,可以通過映射關(guān)系(VM.toThreadState),轉(zhuǎn)換成一個枚舉類。

public enum State {
	NEW,
	RUNNABLE,
	BLOCKED,
	WAITING,
	TIMED_WAITING,
	TERMINATED;
}

java.lang.Thread.State 枚舉類中定義了 6 種線程的狀態(tài),可以調(diào)用線程 Thread 中的 getState() 方法獲取當(dāng)前線程的狀態(tài)。


Java線程狀態(tài)轉(zhuǎn)換圖

NEW (新建狀態(tài))

當(dāng)創(chuàng)建一個線程后,還沒有調(diào)用 start() 方法時,此時這個線程的狀態(tài),是 NEW(初始態(tài))

Thread t = new Thread();

RUNNABLE(運行狀態(tài))

當(dāng) Thread 調(diào)用 start 方法后,線程進入 RUNNABLE 可運行狀態(tài)

在 RUNNABLE 狀態(tài)當(dāng)中又包括了 RUNNING 和 READY 兩種狀態(tài)。

RUNNING(運行中) 和 READY(就緒)

就緒狀態(tài)(READY)

當(dāng)線程對象調(diào)用了 start() 方法之后,線程處于就緒狀態(tài),會存儲在一個就緒隊列中,就緒意味著該線程可以執(zhí)行,但具體啥時候執(zhí)行將取決于 JVM 里線程調(diào)度器的調(diào)度。

It is never legal to start a thread more than once. In particular, a thread may not be restarted once it has completed execution.

這里的意思是:

  • 不允許對一個線程多次使用 start
  • 線程執(zhí)行完成之后,不能試圖用 start 將其喚醒

那么在什么情況下會變成就緒狀態(tài)呢,如下情況:

  • 線程調(diào)用 start(),新建狀態(tài)轉(zhuǎn)化為就緒狀態(tài)
  • 線程 sleep(long) 時間到,等待狀態(tài)轉(zhuǎn)化為就緒狀態(tài)
  • 阻塞式 IO 操作結(jié)果返回,線程變?yōu)榫途w狀態(tài)
  • 其他線程調(diào)用 join() 方法,結(jié)束之后轉(zhuǎn)化為就緒狀態(tài)
  • 線程對象拿到對象鎖之后,也會進入就緒狀態(tài)

運行狀態(tài)(RUNNING)

處于就緒狀態(tài)的線程獲得了 CPU 之后,真正開始執(zhí)行 run() 方法的線程執(zhí)行體時,意味著該線程就已經(jīng)處于運行狀態(tài)。需要注意的是,對于單處理器,一個時刻只能有一個線程處于運行狀態(tài),對于搶占式策略的系統(tǒng)來說,系統(tǒng)會給每個線程一小段時間處理各自的任務(wù)。時間用完之后,系統(tǒng)負(fù)責(zé)奪回線程占用的資源。下一段時間里,系統(tǒng)會根據(jù)一定規(guī)則,再次進行調(diào)度。

那么在什么時候運行狀態(tài)變?yōu)榫途w狀態(tài)的呢?

  • 線程失去處理器資源。線程不一定完整執(zhí)行的,執(zhí)行到一半,說不定就被別的線程搶走了
  • 調(diào)用 yield() 靜態(tài)方法,暫時暫停當(dāng)前線程,讓系統(tǒng)的線程調(diào)度器重新調(diào)度一次,它自己完全有可能再次運行

TERMINATED(終止?fàn)顟B(tài))

當(dāng)一個線程執(zhí)行完畢,線程的狀態(tài)就變?yōu)?TERMINATED。

TERMINATED 終止?fàn)顟B(tài),以下兩種情況會進入這個狀態(tài):

  • 當(dāng)線程的 run() 方法完成時,或者主線程的 main() 方法完成時,我們就認(rèn)為它終止了。這個線程對象也許是活的,但是它已經(jīng)不是一個單獨執(zhí)行的線程。線程一旦終止了,就不能復(fù)生
  • 在一個終止的線程上調(diào)用 start() 方法,會拋出 java.lang.IllegalThreadStateException 異常

為什么會報錯呢,因為 start 方法的已經(jīng)定義好了:

public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    ...
}

NEW、RUNNABLE、TERMINATED 案例

下面用代碼實現(xiàn)看看:

	public static void main(String[] args) throws InterruptedException {
		Thread thread = new Thread();
		System.out.println("創(chuàng)建線程后,線程的狀態(tài)為:"+ thread .getState());
		myThread.start();
		System.out.println("調(diào)用start()方法后線程的狀態(tài)為:"+thread .getState());
		//休眠30毫秒,等待 Thread 線程執(zhí)行完
		Thread.sleep(30);
		System.out.println(Thread.currentThread().getName()+"線程運行");
		System.out.println("執(zhí)行完線程的狀態(tài)為:"+ thread .getState());
	}

創(chuàng)建線程后,線程的狀態(tài)為:NEW

調(diào)用start()方法后線程的狀態(tài)為:RUNNABLE

main線程運行

執(zhí)行完線程的狀態(tài)為:TERMINATED

從以上代碼實現(xiàn)可知:

  • 剛創(chuàng)建完線程后,狀態(tài)為 NEW
  • 調(diào)用了 start() 方法后線程的狀態(tài)變?yōu)?RUNNABLE
  • 然后,我們看到了 run() 方法的執(zhí)行,這個執(zhí)行,是在主線程 main 中調(diào)用 start() 方法后線程的狀態(tài)為 RUNNABLE 輸出后執(zhí)行的
  • 隨后,我們讓 main 線程休眠了30毫秒,等待 Thread 線程退出
  • 最后再打印 Thread 線程的狀態(tài),為 TERMINATED

BLOCKED (阻塞狀態(tài))

在 RUNNABLE狀態(tài) 的線程進入 synchronized 同步塊或者同步方法時,如果獲取鎖失敗,則會進入到 BLOCKED 狀態(tài)。當(dāng)獲取到鎖后,會從 BLOCKED 狀態(tài)恢復(fù)到 RUNNABLE 狀態(tài)。

因此,我們可以得出如下轉(zhuǎn)換關(guān)系:

下面用代碼實現(xiàn)看看:

	public static synchronized void method01() {
		System.out.println(Thread.currentThread().getName()+"開始執(zhí)行主線程的方法");
		try {
			Thread.sleep(10);
		}
		catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"主線程的方法執(zhí)行完畢");
	}
	
	public static void main(String[] args) throws InterruptedException {
		Thread threadA = new Thread(() -> method01(), "A-Thread");
		Thread threadB = new Thread(() -> method01(), "B-Thread");

		threadA.start();
		threadB.start();

		System.out.println("線程 A 的狀態(tài)為:"+ threadA.getState());
		System.out.println("線程 B 的狀態(tài)為:"+ threadB.getState());

	}

A-Thread開始執(zhí)行主線程的方法
線程A的狀態(tài)為:RUNNABLE
線程B的狀態(tài)為:BLOCKED
A-Thread主線程的方法執(zhí)行完畢
B-Thread開始執(zhí)行主線程的方法
B-Thread主線程的方法執(zhí)行完畢

從上面代碼執(zhí)行可知,A 線程優(yōu)先獲得到了鎖,狀態(tài)為 RUNNABLE,這時 B 線程處于 BLOCKED 狀態(tài),當(dāng) A 線程執(zhí)行完畢后,B 線程接著執(zhí)行對應(yīng)方法。

WAITING(等待狀態(tài))

這部分是比較復(fù)雜的,同時也是面試中問得最多的,處于這種狀態(tài)的線程不會被分配 CPU 執(zhí)行時間,它們要等待被顯式地喚醒,否則會處于無限期等待的狀態(tài)。

線程進入 Waiting 狀態(tài)和回到 Runnable 有三種可能性:

wait/notify

沒有設(shè)置 Timeout 參數(shù)的 Object.wait() 方法,如果其他線程調(diào)用 notify() 或 notifyAll() 方法來喚醒它,它會直接進入 Blocked 狀態(tài),因為喚醒 WAITING 線程的線程如果調(diào)用 notify() 或 notifyAll(),要求必須首先持有該 monitor 鎖,所以處于 WAITING 狀態(tài)的線程被喚醒時拿不到該鎖,就會進入 Blocked 狀態(tài),直到執(zhí)行了 notify()/notifyAll() 方法喚醒它的線程執(zhí)行完畢并釋放 monitor 鎖,才可能輪到它去搶奪這把鎖,如果它能搶到,就會從 Blocked 狀態(tài)回到 Runnable 狀態(tài)。

這里的 notify 是只喚醒一個線程,而 notifyAll 是喚醒所有等待隊列中的線程。

join

public class Test {
	
	class ThreadA extends Thread{
        @Override
        public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("線程1執(zhí)行完成");
            }
    }

	public static void main(String[] args) throws InterruptedException {
		Test threadClass = new Test();
		ThreadA threadA = threadClass.new ThreadA();
		threadA.start();

		threadA.join();

        //threadA 線程結(jié)束之后,最后這句才會執(zhí)行,打印主線程名稱
        System.out.println("線程1執(zhí)行完成,主線程"+Thread.currentThread().getName()+"繼續(xù)執(zhí)行");

	}

}

從上面的代碼中,當(dāng)執(zhí)行到 threadA.join() 的時候,(main)主線程會變成 WAITING 狀態(tài),直到線程 threadA 執(zhí)行完畢,主線程才會變回 RUNNABLE 狀態(tài),繼續(xù)往下執(zhí)行。

因此狀態(tài)圖如下:

而 join 阻塞主線程就是通過 wait 和 notifyAll 實現(xiàn)的,我們下面打開 join 的源碼看個究竟:

    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
        //這里的this.isAlive,判斷條件是子線程是否活著,如果活著,當(dāng)前執(zhí)行線程主線程就 wait 阻塞。
            while (isAlive()) {
                wait(0);
            }
        } else {
              //這里的this.isAlive,判斷條件是子線程是否活著,如果活著,當(dāng)前執(zhí)行線程主線程就 wait 阻塞。
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

從源碼中,可以看到 join 是同步方法,鎖對象是當(dāng)前對象即 threadA。所以這里是當(dāng)前線程(main 線程,因為是 main 中調(diào)用的 join 方法)持有了 threadA 對象。isAlive() 是本地方法,非同步。然后判斷的是 threadA 線程是否存活。當(dāng) threadA 線程活著時,調(diào)用wait(0) 方法,這是 Object 類的方法,Object 是超類,所以這里是使持有 threadA 對象的線程阻塞,即 main 線程阻塞。

從 RUNNABLE 到 WAITING,就和執(zhí)行了 wait() 方法完全一樣的,那么從 WAITING 回到 RUNNABLE 是怎么實現(xiàn)的呢?

就是被阻塞的主線程是如何被喚醒的呢?當(dāng)然是線程 threadA 結(jié)束后,由 jvm 自動調(diào)用 t.notifyAll() 喚醒主線程。

那么怎么證明?

當(dāng)子線程執(zhí)行完 run 方法之后,底層在 jvm 源碼里,會執(zhí)行線程的 exit 方法,里面會調(diào)用 notifyAll 方法。

hotspot/src/share/vm/runtime/thread.cpp
void JavaThread::exit(...) {
  ...
  ensure_join(this);
  ...
}
static void ensure_join(JavaThread* thread) {
  ...
  lock.notify_all(thread);
  ...
}

虛擬機在一個線程的方法執(zhí)行完畢后,執(zhí)行了個 ensure_join 方法,這個就是專門為 join 而設(shè)計的。一步步跳進方法中發(fā)現(xiàn)一段關(guān)鍵代碼,lock.notify_all,這便是一個線程結(jié)束后,會自動調(diào)用自己的 notifyAll 方法的證明。

在這里我們可以總結(jié)一點: join 就是 wait,線程結(jié)束就是 notifyAll。

LockSupport.park()/LockSupport.unpark(Thread)

理解了上面 wait 和 notify 的機制,下面就好理解了。

如果一個線程調(diào)用 LockSupport.park() 方法,則該線程狀態(tài)會從 RUNNABLE 變成 WAITING。

另一個線程調(diào)用 LockSupport.unpark(Thread) ,則剛剛的線程會從 WAITING 回到 RUNNABLE。

變化的狀態(tài)圖如下:

TIMED_WAITING(超時等待狀態(tài))

這部分就再簡單不過了,超時等待與等待狀態(tài)一樣,唯一的區(qū)別就是將上面導(dǎo)致線程變成 WAITING 狀態(tài)的那些方法,都增加一個超時參數(shù)。

處于超時等待狀態(tài)中的線程不會被分配 CPU 執(zhí)行時間,必須等待其他相關(guān)線程執(zhí)行完特定的操作或者限時時間結(jié)束后,才有機會再次爭奪 CPU 使用權(quán),將超時等待狀態(tài)的線程轉(zhuǎn)換為運行狀態(tài)。例如,調(diào)用了 wait(long timeout) 方法而處于等待狀態(tài)中的線程,需要通過其他線程調(diào)用 notify() 或者 notifyAll() 方法喚醒當(dāng)前等待中的線程,或者等待限時時間結(jié)束后也可以進行狀態(tài)轉(zhuǎn)換。

變化的狀態(tài)圖如下:

以上就是整個線程狀態(tài)轉(zhuǎn)換圖,到此線程狀態(tài)轉(zhuǎn)換全部講解完。

總結(jié)

線程狀態(tài)轉(zhuǎn)換是寫好多線程代碼的基礎(chǔ),寫這篇文章目的是能夠更加清晰的理解線程狀態(tài)轉(zhuǎn)換,希望對小伙伴有所幫助。

最后來一張簡化的線程轉(zhuǎn)換圖:

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • SharedingSphere?自定義脫敏規(guī)則介紹

    SharedingSphere?自定義脫敏規(guī)則介紹

    這篇文章主要介紹了SharedingSphere?自定義脫敏規(guī)則,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 基于HTTP協(xié)議實現(xiàn)簡單RPC框架的方法詳解

    基于HTTP協(xié)議實現(xiàn)簡單RPC框架的方法詳解

    RPC全名(Remote?Procedure?Call),翻譯過來就是遠程過程調(diào)用,本文將為大家介紹如何基于HTTP協(xié)議實現(xiàn)簡單RPC框架,感興趣的小伙伴可以了解一下
    2023-06-06
  • 詳解Java的readBytes是怎么實現(xiàn)的

    詳解Java的readBytes是怎么實現(xiàn)的

    眾所周知,Java是一門跨平臺語言,針對不同的操作系統(tǒng)有不同的實現(xiàn),下面小編就來從一個非常簡單的api調(diào)用帶大家來看看Java具體是怎么做的吧
    2023-07-07
  • Java多線程同步器代碼詳解

    Java多線程同步器代碼詳解

    這篇文章主要介紹了Java多線程同步器代碼詳解,文章分別介紹了是CountDownLatch,Semaphore,Barrier和Exchanger以及其相關(guān)代碼示例,具有一定參考價值,需要的朋友可以了解下。
    2017-11-11
  • java 中的亂碼問題匯總及解決方案

    java 中的亂碼問題匯總及解決方案

    這篇文章主要介紹了java 中的亂碼問題匯總相關(guān)資料,并附解決方案,出現(xiàn)亂碼問題有編碼與解碼,字節(jié)流與字符流出現(xiàn)亂碼,等其他情況,需要的朋友可以參考下
    2016-11-11
  • 基于SpringBoot解析和生成CSV文件

    基于SpringBoot解析和生成CSV文件

    Apache?Commons?CSV是Apache?Commons項目中的一個子項目,專門用于處理CSV(Comma-Separated?Values,逗號分隔值)文件的Java庫,CSV是一種常見的數(shù)據(jù)交換格式,本文給大家介紹了基于SpringBoot解析和生成CSV文件,需要的朋友可以參考下
    2024-12-12
  • 詳解Java實現(xiàn)緩存(LRU,FIFO)

    詳解Java實現(xiàn)緩存(LRU,FIFO)

    本篇文章主要介紹了詳解Java實現(xiàn)緩存(LRU,FIFO) ,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-04-04
  • java 將一個數(shù)組逆序輸出的方法

    java 將一個數(shù)組逆序輸出的方法

    今天小編就為大家分享一篇java 將一個數(shù)組逆序輸出的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-06-06
  • Java List按照某字段去重的使用示例

    Java List按照某字段去重的使用示例

    在Java開發(fā)中,我們經(jīng)常會面臨對List中對象屬性去重的需求,本文主要介紹了Java List按照某字段去重的使用示例,具有一定的參考價值,感興趣的可以了解一下
    2023-12-12
  • Spring避免循環(huán)依賴的策略詳解

    Spring避免循環(huán)依賴的策略詳解

    在Spring框架中,循環(huán)依賴是指兩個或多個bean相互依賴對方,形成一個閉環(huán),這在應(yīng)用啟動時可能導(dǎo)致BeanCurrentlyInCreationException異常,本文給大家介紹了Spring中如何避免循環(huán)依賴,需要的朋友可以參考下
    2024-02-02

最新評論