欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

前言

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

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

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

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)建一個(gè)線程后,還沒有調(diào)用 start() 方法時(shí),此時(shí)這個(gè)線程的狀態(tài),是 NEW(初始態(tài))

Thread t = new Thread();

RUNNABLE(運(yùn)行狀態(tài))

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

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

RUNNING(運(yùn)行中) 和 READY(就緒)

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

當(dāng)線程對(duì)象調(diào)用了 start() 方法之后,線程處于就緒狀態(tài),會(huì)存儲(chǔ)在一個(gè)就緒隊(duì)列中,就緒意味著該線程可以執(zhí)行,但具體啥時(shí)候執(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.

這里的意思是:

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

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

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

運(yùn)行狀態(tài)(RUNNING)

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

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

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

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

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

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

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

為什么會(huì)報(bào)錯(cuò)呢,因?yàn)?start 方法的已經(jīng)定義好了:

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

NEW、RUNNABLE、TERMINATED 案例

下面用代碼實(shí)現(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()+"線程運(yùn)行");
		System.out.println("執(zhí)行完線程的狀態(tài)為:"+ thread .getState());
	}

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

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

main線程運(yùn)行

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

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

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

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

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

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

下面用代碼實(shí)現(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,這時(shí) B 線程處于 BLOCKED 狀態(tài),當(dāng) A 線程執(zhí)行完畢后,B 線程接著執(zhí)行對(duì)應(yīng)方法。

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

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

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

wait/notify

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

這里的 notify 是只喚醒一個(gè)線程,而 notifyAll 是喚醒所有等待隊(duì)列中的線程。

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é)束之后,最后這句才會(huì)執(zhí)行,打印主線程名稱
        System.out.println("線程1執(zhí)行完成,主線程"+Thread.currentThread().getName()+"繼續(xù)執(zhí)行");

	}

}

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

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

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

    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 是同步方法,鎖對(duì)象是當(dāng)前對(duì)象即 threadA。所以這里是當(dāng)前線程(main 線程,因?yàn)槭?main 中調(diào)用的 join 方法)持有了 threadA 對(duì)象。isAlive() 是本地方法,非同步。然后判斷的是 threadA 線程是否存活。當(dāng) threadA 線程活著時(shí),調(diào)用wait(0) 方法,這是 Object 類的方法,Object 是超類,所以這里是使持有 threadA 對(duì)象的線程阻塞,即 main 線程阻塞。

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

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

那么怎么證明?

當(dāng)子線程執(zhí)行完 run 方法之后,底層在 jvm 源碼里,會(huì)執(zhí)行線程的 exit 方法,里面會(huì)調(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);
  ...
}

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

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

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

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

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

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

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

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

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

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

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

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

總結(jié)

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

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

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

相關(guān)文章

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

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

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

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

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

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

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

    Java多線程同步器代碼詳解

    這篇文章主要介紹了Java多線程同步器代碼詳解,文章分別介紹了是CountDownLatch,Semaphore,Barrier和Exchanger以及其相關(guān)代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。
    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項(xiàng)目中的一個(gè)子項(xiàng)目,專門用于處理CSV(Comma-Separated?Values,逗號(hào)分隔值)文件的Java庫,CSV是一種常見的數(shù)據(jù)交換格式,本文給大家介紹了基于SpringBoot解析和生成CSV文件,需要的朋友可以參考下
    2024-12-12
  • 詳解Java實(shí)現(xiàn)緩存(LRU,FIFO)

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

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

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

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

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

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

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

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

最新評(píng)論