日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
Java線程的狀態(tài)及轉(zhuǎn)換

低并發(fā)編程

為江寧等地區(qū)用戶提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及江寧網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)、江寧網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!

戰(zhàn)略上藐視技術(shù),戰(zhàn)術(shù)上重視技術(shù)

閃客:小宇你怎么了,我看你臉色很不好呀。

小宇:今天去面試了,面試官問(wèn)我 Java 線程的狀態(tài)及其轉(zhuǎn)化。

閃客:哦哦,很常見(jiàn)的面試題呀,不是有一張狀態(tài)流轉(zhuǎn)圖嘛。

小宇:我知道,可是我每次面試的時(shí)候,腦子里記過(guò)的流轉(zhuǎn)圖就變成這樣了。

閃客:哈哈哈。

小宇:你還笑,氣死我了,你能不能給我講講這些亂七八糟的狀態(tài)呀。

閃客:沒(méi)問(wèn)題,還是老規(guī)矩,你先把所有狀態(tài)都忘掉,聽(tīng)我從頭道來(lái)!

小宇:好滴。

線程狀態(tài)的實(shí)質(zhì)

首先你得明白,當(dāng)我們說(shuō)一個(gè)線程的狀態(tài)時(shí),說(shuō)的是什么?

沒(méi)錯(cuò),就是一個(gè)變量的值而已。

哪個(gè)變量?

Thread 類中的一個(gè)變量,叫

private volatile int threadStatus = 0;

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

public enum State {

NEW,

RUNNABLE,

BLOCKED,

WAITING,

TIMED_WAITING,

TERMINATED;

}

所以,我們就盯著 threadStatus 這個(gè)值的變化就好了。

就是這么簡(jiǎn)單。

NEW

現(xiàn)在我們還沒(méi)有任何 Thread 類的對(duì)象呢,也就不存在線程狀態(tài)一說(shuō)。

一切的起點(diǎn),要從把一個(gè) Thread 類的對(duì)象創(chuàng)建出來(lái),開始說(shuō)起。

Thread t = new Thread();

當(dāng)然,你后面可以接很多參數(shù)。

Thread t = new Thread(r, "name1");

你也可以 new 一個(gè)繼承了 Thread 類的子類。

Thread t = new MyThread();

你說(shuō)線程池怎么不 new 就可以有線程了呢?人家內(nèi)部也是 new 出來(lái)的。

public class Executors {
static class DefaultThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread t = new Thread();
return t;
}
}
public class Executors {
static class DefaultThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread t = new Thread();
return t;
}
}
public class Executors {
static class DefaultThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread t = new Thread();
return t;
}
}
}

總是,一切的開始,都要調(diào)用 Thread 類的構(gòu)造方法。

而這個(gè)構(gòu)造方法,最終都會(huì)調(diào)用 Thread 類的 init () 方法。

private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
this.grout = g;
this.name = name;
tid = nextThreadID();
}

這個(gè) init 方法,僅僅是給該 Thread 類的對(duì)象中的屬性,附上值,除此之外啥也沒(méi)干。

它沒(méi)有給 theadStatus 再次賦值,所以它的值仍然是其默認(rèn)值。

而這個(gè)值對(duì)應(yīng)的狀態(tài),就是 STATE.NEW,非要翻譯成中文,就叫初始態(tài)吧。

因此說(shuō)了這么多,其實(shí)就分析出了,新建一個(gè) Thread 類的對(duì)象,就是創(chuàng)建了一個(gè)新的線程,此時(shí)這個(gè)線程的狀態(tài),是 NEW(初始態(tài))。

之后的分析,將弱化 threadStatus 這個(gè)整數(shù)值了,就直接說(shuō)改變了其線程狀態(tài),大家知道其實(shí)就只是改變了 threadStatus 的值而已。

RUNNABLE

你說(shuō),剛剛處于 NEW 狀態(tài)的線程,對(duì)應(yīng)操作系統(tǒng)里的什么狀態(tài)呢?

一看你就沒(méi)仔細(xì)看我上面的分析。

Thread t = new Thread();

只是做了些表面功夫,在 Java 語(yǔ)言層面將自己的一個(gè)對(duì)象中的屬性附上值罷了,根本沒(méi)碰到操作系統(tǒng)級(jí)別的東西呢。

所以這個(gè) NEW 狀態(tài),不論往深了說(shuō)還是往淺了說(shuō),還真就只是個(gè)無(wú)聊的枚舉值而已。

下面,精彩的故事才剛剛開始。

躺在堆內(nèi)存中無(wú)所事事的 Thread 對(duì)象,在調(diào)用了 start () 方法后,才顯現(xiàn)生機(jī)。

t.start();

這個(gè)方法一調(diào)用,那可不得了,最終會(huì)調(diào)用到一個(gè)討厭的 native 方法里。

private native void start0();

看來(lái)改變狀態(tài)就并不是一句 threadStatus = xxx 這么簡(jiǎn)單了,而是有本地方法對(duì)其進(jìn)行了修改。

九曲十八彎跟進(jìn) jvm 源碼之后,調(diào)用到了這個(gè)方法。

hotspot/src/os/linux/vm/os_linux.cpp
pthread_create();

大名鼎鼎的 unix 創(chuàng)建線程的方法,pthread_create。

此時(shí),在操作系統(tǒng)內(nèi)核中,才有了一個(gè)真正的線程,被創(chuàng)建出來(lái)。

而 linux 操作系統(tǒng),是沒(méi)有所謂的剛創(chuàng)建但沒(méi)啟動(dòng)的線程這種說(shuō)法的,創(chuàng)建即刻開始運(yùn)行。

雖然無(wú)法從源碼發(fā)現(xiàn)線程狀態(tài)的變化,但通過(guò) debug 的方式,我們看到調(diào)用了 Thread.start () 方法后,線程的狀態(tài)變成了 RUNNABLE,運(yùn)行態(tài)。

那我們的狀態(tài)圖又豐富了起來(lái)。

通過(guò)這部分,我們知道如下幾點(diǎn):

1. 在 Java 調(diào)用 start () 后,操作系統(tǒng)中才真正出現(xiàn)了一個(gè)線程,并且立刻運(yùn)行。

2. Java 中的線程,和操作系統(tǒng)內(nèi)核中的線程,是一對(duì)一的關(guān)系。

3. 調(diào)用 start 后,線程狀態(tài)變?yōu)?RUNNABLE,這是由 native 方法里的某部分代碼造成的。

RUNNING 和 READY

CPU 一個(gè)核心,同一時(shí)刻,只能運(yùn)行一個(gè)線程。

具體執(zhí)行哪個(gè)線程,要看操作系統(tǒng) 的調(diào)度機(jī)制。

所以,上面的 RUNNABLE 狀態(tài),準(zhǔn)確說(shuō)是,得到了可以隨時(shí)準(zhǔn)備運(yùn)行的機(jī)會(huì)的狀態(tài)。

而處于這個(gè)狀態(tài)中的線程,也分為了正在 CPU 中運(yùn)行的線程,和一堆處于就緒中等待 CPU 分配時(shí)間片來(lái)運(yùn)行的線程。

處于就緒中的線程,會(huì)存儲(chǔ)在一個(gè)就緒隊(duì)列中,等待著被操作系統(tǒng)的調(diào)度機(jī)制選到,進(jìn)入 CPU 中運(yùn)行。

當(dāng)然,要注意,這里的 RUNNING 和 READY 狀態(tài),是我們自己為了方便描述而造出來(lái)的。

無(wú)論是 Java 語(yǔ)言,還是操作系統(tǒng),都不區(qū)分這兩種狀態(tài),在 Java 中統(tǒng)統(tǒng)叫 RUNNABLE。

TERMINATED

當(dāng)一個(gè)線程執(zhí)行完畢(或者調(diào)用已經(jīng)不建議的 stop 方法),線程的狀態(tài)就變?yōu)?TERMINATED。

此時(shí)這個(gè)線程已經(jīng)無(wú)法死灰復(fù)燃了,如果你此時(shí)再?gòu)?qiáng)行執(zhí)行 start 方法,將會(huì)報(bào)出錯(cuò)誤。

java.lang.IllegalThreadStateException

很簡(jiǎn)單,因?yàn)?start 方法的第一行就是這么直戳了當(dāng)?shù)貙懙摹?/p>

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

誒,那如果此時(shí)強(qiáng)行把 threadStatus 改成 0,會(huì)怎么樣呢?你可以試試喲。

BLOCKED

上面把最常見(jiàn),最簡(jiǎn)單的線程生命周期講完了。

初始 -- 運(yùn)行 -- 終止

沒(méi)有發(fā)生任何的障礙。

接下來(lái),就稍稍復(fù)雜一點(diǎn)了,我們讓線程碰到些障礙。

首先創(chuàng)建一個(gè)對(duì)象 lock。

public static final Object lock = new Object();

一個(gè)線程,執(zhí)行一個(gè) sychronized 塊,鎖對(duì)象是 lock,且一直持有這把鎖不放。

new Thread(() - {
synchronized (lock) {
while(true) {}
}
}).start();

另一個(gè)線程,也同樣執(zhí)行一個(gè)鎖對(duì)象為 lock 的 sychronized 塊。

new Thread(() - {
synchronized (lock) {
...
}
}).start();

那么,在進(jìn)入 synchronized 塊時(shí),因?yàn)闊o(wú)法拿到鎖,會(huì)使線程狀態(tài)變?yōu)?BLOCKED。

同樣,對(duì)于 synchronized 方法,也是如此。

當(dāng)該線程獲取到了鎖后,便可以進(jìn)入 synchronized 塊,此時(shí)線程狀態(tài)變?yōu)?RUNNABLE。

因此我們得出如下轉(zhuǎn)換關(guān)系。

當(dāng)然,這只是線程狀態(tài)的改變,線程還發(fā)生了一些實(shí)質(zhì)性的變化。

我們不考慮虛擬機(jī)對(duì) synchronized 的極致優(yōu)化。

當(dāng)進(jìn)入 synchronized 塊或方法,獲取不到鎖時(shí),線程會(huì)進(jìn)入一個(gè)該鎖對(duì)象的同步隊(duì)列。

當(dāng)持有鎖的這個(gè)線程,釋放了鎖之后,會(huì)喚醒該鎖對(duì)象同步隊(duì)列中的所有線程,這些線程會(huì)繼續(xù)嘗試搶鎖。如此往復(fù)。

比如,有一個(gè)鎖對(duì)象 A,線程 1 此時(shí)持有這把鎖。線程 2、3、4 分別嘗試搶這把鎖失敗。

線程 1 釋放鎖,線程 2、3、4 重新變?yōu)?RUNNABLE,繼續(xù)搶鎖,假如此時(shí)線程 3 搶到了鎖。

如此往復(fù)。

WAITING

這部分是最復(fù)雜的,同時(shí)也是面試中考點(diǎn)最多的,將分成三部分講解。聽(tīng)我說(shuō)完后你會(huì)發(fā)現(xiàn),這三部分有很多相同但地方,不再是孤立的知識(shí)點(diǎn)。

wait/notify

我們?cè)趧倓偟?synchronized 塊中加點(diǎn)東西。

new Thread(() - {
synchronized (lock) {
...
lock.wait();
...
}
}).start();

當(dāng)這個(gè) lock.wait () 方法一調(diào)用,會(huì)發(fā)生三件事。

1. 釋放鎖對(duì)象 lock(隱含著必須先獲取到這個(gè)鎖才行)

2. 線程狀態(tài)變成 WAITING

3. 線程進(jìn)入 lock 對(duì)象的等待隊(duì)列

什么時(shí)候這個(gè)線程被喚醒,從等待隊(duì)列中移出,并從 WAITING 狀態(tài)返回 RUNNABLE 狀態(tài)呢?

必須由另一個(gè)線程,調(diào)用同一個(gè)對(duì)象的 notify / notifyAll 方法。

new Thread(() - {
synchronized (lock) {
...
lock.notify();
...
}
}).start();

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

但需要注意,被喚醒后的線程,從等待隊(duì)列移出,狀態(tài)變?yōu)?RUNNABLE,但仍然需要搶鎖,搶鎖成功了,才可以從 wait 方法返回,繼續(xù)執(zhí)行。

如果失敗了,就和上一部分的 BLOCKED 流程一樣了。

所以我們的整個(gè)流程圖,現(xiàn)在變成了這個(gè)樣子。

join

主線程這樣寫。

public static void main(String[] args) {
thread t = new Thread();
t.start();
t.join();
}

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

看起來(lái),就像是主線程執(zhí)行過(guò)程中,另一個(gè)線程插隊(duì)加入(join),而且要等到其結(jié)束后主線程才繼續(xù)。

因此我們的狀態(tài)圖,又多了兩項(xiàng)。

那 join 又是怎么神奇地實(shí)現(xiàn)這一切呢?也是像 wait 一樣放到等待隊(duì)列么?

打開 Thread.join () 的源碼,你會(huì)發(fā)現(xiàn)它非常簡(jiǎn)單。

// Thread.java
// 無(wú)參的 join 有用的信息就這些,省略了額外分支
public synchronized void join() {
while (isAlive()) {
wait();
}
}

也就是說(shuō),他的本質(zhì)仍然是執(zhí)行了 wait () 方法,而鎖對(duì)象就是 Thread t 對(duì)象本身。

那從 RUNNABLE 到 WAITING,就和執(zhí)行了 wait () 方法完全一樣了。

那從 WAITING 回到 RUNNABLE 是怎么實(shí)現(xiàn)的呢?

主線程調(diào)用了 wait ,需要另一個(gè)線程 notify 才行,難道需要這個(gè)子線程 t 在結(jié)束之前,調(diào)用一下 t.notifyAll () 么?

答案是否定的,那就只有一種可能,線程 t 結(jié)束后,由 jvm 自動(dòng)調(diào)用 t.notifyAll (),不用我們程序顯示寫出。

沒(méi)錯(cuò),就是這樣。

怎么證明這一點(diǎn)呢?道聽(tīng)途說(shuō)可不行,老子今天非要扒開 jvm 的外套。

果然,找到了如下代碼。

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 方法,看名字就知道是專門為 join 而設(shè)計(jì)的。

而繼續(xù)跟進(jìn)會(huì)發(fā)現(xiàn)一段關(guān)鍵代碼,lock.notify_all,這便是一個(gè)線程結(jié)束后,會(huì)自動(dòng)調(diào)用自己的 notifyAll 方法的證明。

所以,其實(shí) join 就是 wait,線程結(jié)束就是 notifyAll?,F(xiàn)在,是不是更清晰了。

park/unpark

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

一個(gè)線程調(diào)用如下方法。

LockSupport.park()

該線程狀態(tài)會(huì)從 RUNNABLE 變成 WAITING、

另一個(gè)線程調(diào)用

LockSupport.unpark (Thread 剛剛的線程)

剛剛的線程會(huì)從 WAITING 回到 RUNNABLE

但從線程狀態(tài)流轉(zhuǎn)來(lái)看,與 wait 和 notify 相同。

從實(shí)現(xiàn)機(jī)制上看,他們甚至更為簡(jiǎn)單。

1. park 和 unpark 無(wú)需事先獲取鎖,或者說(shuō)跟鎖壓根無(wú)關(guān)。

2. 沒(méi)有什么等待隊(duì)列一說(shuō),unpark 會(huì)精準(zhǔn)喚醒某一個(gè)確定的線程。

3. park 和 unpark 沒(méi)有順序要求,可以先調(diào)用 unpark

關(guān)于第三點(diǎn),就涉及到 park 的原理了,這里我只簡(jiǎn)單說(shuō)明。

線程有一個(gè)計(jì)數(shù)器,初始值為 0

調(diào)用 park 就是

如果這個(gè)值為 0,就將線程掛起,狀態(tài)改為 WAITING。如果這個(gè)值為 1,則將這個(gè)值改為 0,其余的什么都不做。

調(diào)用 unpark 就是

將這個(gè)值改為 1

然后我用三個(gè)例子,你就基本明白了。

// 例子1
LockSupport.unpark(Thread.currentThread()); // 1
LockSupport.park(); // 0
System.out.println("可以運(yùn)行到這");
// 例子2
LockSupport.unpark(Thread.currentThread()); // 1
LockSupport.unpark(Thread.currentThread()); // 1
LockSupport.park(); // 0
System.out.println("可以運(yùn)行到這");
// 例子3
LockSupport.unpark(Thread.currentThread()); // 1
LockSupport.unpark(Thread.currentThread()); // 1
LockSupport.park(); // 0
LockSupport.park(); // WAITING
System.out.println("不可以運(yùn)行到這");

park 的使用非常簡(jiǎn)單,同時(shí)也是 JDK 中鎖實(shí)現(xiàn)的底層。它的 JVM 及操作系統(tǒng)層面的原理很復(fù)雜,改天可以專門找一節(jié)來(lái)講解。

現(xiàn)在我們的狀態(tài)圖,又可以更新了。

TIMED_WAITING

這部分就再簡(jiǎn)單不過(guò)了,將上面導(dǎo)致線程變成 WAITING 狀態(tài)的那些方法,都增加一個(gè)超時(shí)參數(shù),就變成了將線程變成 TIMED_WAITING 狀態(tài)的方法了,我們直接更新流程圖。

這些方法的唯一區(qū)別就是,從 TIMED_WAITING 返回 RUNNABLE,不但可以通過(guò)之前的方式,還可以通過(guò)到了超時(shí)時(shí)間,返回 RUNNABLE 狀態(tài)。

就這樣。

還有,大家看。

wait 需要先獲取鎖,再釋放鎖,然后等待被 notify。

join 就是 wait 的封裝。

park 需要等待 unpark 來(lái)喚醒,或者提前被 unpark 發(fā)放了喚醒許可。

那有沒(méi)有一個(gè)方法,僅僅讓線程掛起,只能通過(guò)等待超時(shí)時(shí)間到了再被喚醒呢。

這個(gè)方法就是

Thread.sleep(long)

我們把它補(bǔ)充在圖里,這一部分就全了。

再把它加到全局圖中。

后記

Java 線程的狀態(tài),有六種

  1. NEW
  2. RUNNABLE
  3. BLOCKED
  4. WAITING
  5. TIMED_WAITING
  6. TERMINATED

而經(jīng)典的線程五態(tài)模型,有五種狀態(tài)

  1. 創(chuàng)建
  2. 就緒
  3. 執(zhí)行
  4. 阻塞
  5. 終止

不同實(shí)現(xiàn)者,可能有合并和拆分。

比如 Java 將五態(tài)模型中的就緒和執(zhí)行,都統(tǒng)一成 RUNNABLE,將阻塞(即不可能得到 CPU 運(yùn)行機(jī)會(huì)的狀態(tài))細(xì)分為了 BLOCKED、WAITING、TIMED_WAITING,這里我們不去評(píng)價(jià)好壞。

也就是說(shuō),BLOCKED、WAITING、TIMED_WAITING 這幾個(gè)狀態(tài),線程都不可能得到 CPU 的運(yùn)行權(quán),你叫它掛起、阻塞、睡眠、等待,都可以,很多文章,你也會(huì)看到這幾個(gè)詞沒(méi)那么較真地來(lái)回用。

再說(shuō)兩個(gè)你可能困惑的問(wèn)題。

調(diào)用 jdk 的 Lock 接口中的 lock,如果獲取不到鎖,線程將掛起,此時(shí)線程的狀態(tài)是什么呢?

有多少同學(xué)覺(jué)得應(yīng)該和 synchronized 獲取不到鎖的效果一樣,是變成 BLOCKED 狀態(tài)?

不過(guò)如果你仔細(xì)看我上面的文章,有一句話提到了,jdk 中鎖的實(shí)現(xiàn),是基于 AQS 的,而 AQS 的底層,是用 park 和 unpark 來(lái)掛起和喚醒線程,所以應(yīng)該是變?yōu)?WAITING 或 TIMED_WAITING 狀態(tài)。

調(diào)用阻塞 IO 方法,線程變成什么狀態(tài)?

比如 socket 編程時(shí),調(diào)用如 accept (),read () 這種阻塞方法時(shí),線程處于什么狀態(tài)呢?

答案是處于 RUNNABLE 狀態(tài),但實(shí)際上這個(gè)線程是得不到運(yùn)行權(quán)的,因?yàn)樵诓僮飨到y(tǒng)層面處于阻塞態(tài),需要等到 IO 就緒,才能變?yōu)榫途w態(tài)。

但是在 Java 層面,JVM 認(rèn)為等待 IO 與等待 CPU 執(zhí)行權(quán),都是一樣的,人家就是這么認(rèn)為的,這里我仍然不討論其好壞,你覺(jué)得這么認(rèn)為不爽,可以自己設(shè)計(jì)一門語(yǔ)言,那你想怎么認(rèn)為,別人也拿你沒(méi)辦法。

比如要我設(shè)計(jì)語(yǔ)言,我就認(rèn)為可被 CPU 調(diào)度執(zhí)行的線程,處于死亡態(tài)。這樣我的這門語(yǔ)言一定會(huì)有個(gè)經(jīng)典面試題,為什么閃客把可運(yùn)行的線程定義為死亡態(tài)呢?

OK,今天的文章就到這里。

本篇文章寫得有點(diǎn)投入,寫到這發(fā)現(xiàn)把開頭都小宇都給忘了。


本文名稱:Java線程的狀態(tài)及轉(zhuǎn)換
文章轉(zhuǎn)載:http://www.dlmjj.cn/article/coiiidh.html