【版權(quán)聲明】未經(jīng)博主同意,謝絕轉(zhuǎn)載?。ㄕ堊鹬卦瓌?chuàng),博主保留追究權(quán))
https://www.cnblogs.com/cnb-yuchen/p/18162522
出自【進(jìn)步*于辰的博客】
線程生命周期與進(jìn)程有諸多相似,所以我們很容易將兩者關(guān)聯(lián)理解并混淆,一些細(xì)節(jié)之處確有許多不同,因?yàn)榫€程調(diào)度與進(jìn)程調(diào)度雖都由CPU完成,但兩者并不相同。
特意耗費(fèi)一些時(shí)間,系統(tǒng)地對線程生命周期與線程通信進(jìn)行梳理、整理。
-
1、線程生命周期
- 1.1 JDK1.8版本
- 1.2 早期版本(JDK1.2之前)
- 1.3 落到實(shí)處
-
2、線程通信
- 2.1 使用 volatile 關(guān)鍵字
- 2.2 使用Object類的
wait()
和notify()
- 2.3 使用JUC工具類 CountDownLatch
- 2.4 使用 ReentrantLock 結(jié)合 Condition
- 2.5 使用 LockSupport
- 最后
1、線程生命周期
1.1 JDK1.8版本
啟發(fā)博文:《線程的生命周期及五種基本狀態(tài)》(轉(zhuǎn)發(fā))。
引用其中一張線程生命周期圖:
在啟發(fā)博文中,博主對線程五大狀態(tài)和生命周期進(jìn)行了很詳細(xì)的說明,大家可以先行查閱。
這張圖對線程生命周期總結(jié)得比較全面,我一一梳理、核對后覺得稍有不妥之處,略作修改后作如下圖:
在此我先簡述一下我對線程五個(gè)狀態(tài)的理解:
-
new
(新建):在線程創(chuàng)建后、啟動(dòng)(start()
)之前所處的狀態(tài)。 -
Runnable
(就緒):線程新建后并不是直接開始運(yùn)行,而是被加入到等待隊(duì)列,等待線程調(diào)度(等待CPU),此時(shí)就處于就緒狀態(tài)。因此,這兩種情況將進(jìn)入就緒狀態(tài):(1)調(diào)用start()
;(2)因某種原因(如:線程通信、等待IO)進(jìn)入阻塞狀態(tài)后重新等待運(yùn)行。 -
Running
(運(yùn)行):線程正在運(yùn)行時(shí)的狀態(tài)。 -
Blocked
(阻塞):線程因某種原因(如:線程通信、等待IO)而停止運(yùn)行后進(jìn)入的狀態(tài)。 -
Dead
(死亡):線程正常結(jié)束或異常終止后所處的狀態(tài)。
相信大家在閱讀完以上簡述后,對線程的五大狀態(tài)已經(jīng)有了一個(gè)初步的認(rèn)識(shí),那狀態(tài)間是如何轉(zhuǎn)換的?又怎么理解呢?對于這兩個(gè)問題,由于涉及到各個(gè)方法的業(yè)務(wù)和底層邏輯,本篇文章不便一一詳述。如果大家想要進(jìn)一步了解,可移步 → 《Thread類源碼解析》。
其中,Blocked
狀態(tài)可能不太好理解,那位博主將其劃分為三種情況:等待阻塞、同步阻塞和其他阻塞。我贊同,大家可移步啟發(fā)博文查閱詳述,在此不贅述,僅稍作說明:
三種阻塞情況的變動(dòng)主要因“線程通信”引起,變化僅是阻塞情況的變化,狀態(tài)不變,仍是
Blocked
。
點(diǎn)出兩個(gè)問題:
1:為什么調(diào)用notify()/notifyAll()
,線程由等待Blocked
變?yōu)?code>鎖定Blocked?
文章排版考慮,在下文【使用Object類的wait()
和notify()
】中說明。
2:interrupt()
可中斷線程,那么可中斷正在阻塞的線程嗎?
本質(zhì)上說,可以,但會(huì)拋出異常(即不可以,故我未將其寫入上圖),在上文我給出的《Thread類源碼解析》文章中有具體說明。
1.2 早期版本(JDK1.2之前)
相信能堅(jiān)持閱讀到這的博友,大部分是站在Java門檻上或剛?cè)腴T不久的Java小白,你們現(xiàn)在了解和學(xué)習(xí)線程生命周期,獲得的是已更新、迭代后的知識(shí)。個(gè)人認(rèn)為,大家不需要掌握已過時(shí)的知識(shí),但不能不了解,我先拋出兩個(gè)問題:
- “掛起”狀態(tài)是什么?怎么不在線程五大狀態(tài)之列?
- 相信大家在一些資料中,可能見到過
suspend()、resume()、stop()
或destroy()
這4個(gè)方法,怎么上圖中沒有?為什么不用了?
當(dāng)然是有的,只是過時(shí)了,所以沒放上去,完整的圖是這樣:
OK,現(xiàn)在回答那兩個(gè)問題。
“掛起”狀態(tài)是一種類似
Runnable
(就緒)狀態(tài)的狀態(tài),不同之處是進(jìn)入就緒狀態(tài)的線程,會(huì)釋放所持有的“同步鎖”,而“掛起”狀態(tài)不會(huì),“掛起”狀態(tài)相當(dāng)于“暫?!?,故容易導(dǎo)致“死鎖”。
為什么那4個(gè)方法會(huì)被放棄?
我尋得一答案,闡述得很詳細(xì),我便不班門弄斧了,看這里 → 《《Java面向?qū)ο缶幊獭穼?dǎo)讀-Thread類的被廢棄的suspend()、resume()和stop()方法》(轉(zhuǎn)發(fā))。
我補(bǔ)充一張圖:
1.3 落到實(shí)處
所謂“落到實(shí)處”,就是要想掌握線程生命周期,光如上文夸夸其談當(dāng)然還不夠,我們要把線程五大狀態(tài)和狀態(tài)間轉(zhuǎn)換對應(yīng)到Thread源碼中才行。
如下圖:
我自己感覺有點(diǎn)亂,源碼所示如此。
當(dāng)然,這不是完整圖,圖中狀態(tài)間轉(zhuǎn)換僅做了部分舉例。在此,我不作說明,相信用心看到這里的博友可以大致理解。當(dāng)然,也不便做出說明,因?yàn)槲夷壳皩σ恍┓椒ǖ牧私馔A粼凇皶?huì)用”的程度(見下文),并未對相應(yīng)源碼進(jìn)行解析。
補(bǔ)充一點(diǎn):
大家對比這張“狀態(tài)圖”和上文線程生命周期圖,大家會(huì)發(fā)現(xiàn)有點(diǎn)對不上。
其實(shí),WAITING
就是Runnable
(就緒),在線程生命周期中,一般不說“就緒”,“就緒”是進(jìn)程生命周期中的術(shù)語,上文這般使用是為了方便大家理解;而RUNNABLE
就是Running
。
2、線程通信
啟發(fā)博文:《線程間通信的幾種實(shí)現(xiàn)方式》(轉(zhuǎn)發(fā))。
我暫未整理“線程通信”相關(guān)理論,故下文將以示例的形式進(jìn)行闡述。
注:以下5個(gè)示例都成功實(shí)現(xiàn)線程通信,輸出結(jié)果是:
喚醒t1
t1已喚醒
2.1 使用 volatile 關(guān)鍵字
示例:
private static volatile boolean isWait = true;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true)
if (!isWait) {
System.out.println("t1已喚醒");
break;
}
});
Thread t2 = new Thread(() -> {
System.out.println("喚醒t1");
isWait = false;
});
t1.start();
t2.start();
}
如果大家不了解
volatile
關(guān)鍵字,看這里。
這里線程通信利用的是volatile關(guān)鍵字“保證可見性”的原理。
2.2 使用Object類的wait()和notify()
示例:
Object lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1已喚醒");
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("喚醒t1");
// lock.notify();// 喚醒等待隊(duì)列中的一個(gè)線程,不一定是 t1
lock.notifyAll();
}
});
t1.start();
t2.start();
大家還記得我在【1.1】中點(diǎn)出的這個(gè)問題嗎?
為什么調(diào)用
notify()/notifyAll()
,線程由等待Blocked
變?yōu)?code>鎖定Blocked?
答案就在以上代碼的執(zhí)行過程中,我給大家捋一捋。
1、t1、t2都執(zhí)行,t1在t2之前啟動(dòng),先獲得同步鎖,t2阻塞。
2、t1調(diào)用wait()進(jìn)入等待狀態(tài),釋放同步鎖,同步鎖由t2獲得,t2開始運(yùn)行。
3、t2調(diào)用notify()喚醒t1,但此時(shí)同步鎖仍由t2持有,t1繼續(xù)等待。
4、t2運(yùn)行完,釋放同步鎖,由t1獲得,t1開始運(yùn)行。
OK,就是第3點(diǎn)。
為什么一定要同步鎖?
因?yàn)?code>wait()與notify()
的底層邏輯要求必須是“先等待,再喚醒”,同步鎖可以保證流程的正常執(zhí)行。難道真的不能去掉同步鎖?例如這樣:
Object lock = new Object();
Thread t1 = new Thread(() -> {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1已喚醒");
});
Thread t2 = new Thread(() -> {
System.out.println("喚醒t1");
lock.notifyAll();
});
t1.start();
t1.join();
t2.start();
很明顯,不行。這樣就出現(xiàn)了“死鎖”。
t1 等待被喚醒,主線程等待 t1 運(yùn)行完。
因此,必須使用同步鎖,且必須是同一把鎖(lock
)、
2.3 使用JUC工具類 CountDownLatch
示例:
CountDownLatch latch = new CountDownLatch(1);// 這個(gè) 1 是同步狀態(tài),類似synchronized中的 count
Thread t1 = new Thread(() -> {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1已喚醒");
});
Thread t2 = new Thread(() -> {
System.out.println("喚醒t1");
latch.countDown();
});
t1.start();
t2.start();
可見,無需同步鎖。為何?這就要涉及CountDownLatch類的源碼了。當(dāng)然,我們暫且不用深入了解,理解其底層邏輯即可。
看這里 → 《這一次,徹底搞懂Java中的synchronized關(guān)鍵字》(轉(zhuǎn)發(fā))。
大家找到【1.同步代碼塊】這一欄,底層邏輯相似。
2.4 使用 ReentrantLock 結(jié)合 Condition
示例:
Lock lock = new ReentrantLock();
Condition cond = lock.newCondition();
Thread t1 = new Thread(() -> {
lock.lock();
try {
cond.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1已喚醒");
lock.unlock();
});
Thread t2 = new Thread(() -> {
lock.lock();
System.out.println("喚醒t1");
cond.signal();
lock.unlock();
});
t1.start();
t2.start();
這兩條代碼合起來相當(dāng)于同步鎖:
lock.lock();
...
lock.unlock();
2.5 使用 LockSupport
示例:
Thread t1 = new Thread(() -> {
LockSupport.park();
System.out.println("t1已喚醒");
});
Thread t2 = new Thread(() -> {
System.out.println("喚醒t1");
LockSupport.unpark(t1);
});
t1.start();
t2.start();
可見,LockSupport類不關(guān)注是否“在等待”。
最后
本文中的例子是為了方便大家理解和闡述知識(shí)點(diǎn)而簡單舉出的,旨在“闡明知識(shí)點(diǎn)”,簡單為主,并不一定有實(shí)用性。
如果大家想要快速地掌握這些知識(shí)點(diǎn),我的建議是“自測中理解”。文章來源:http://www.zghlxwxcb.cn/news/detail-860284.html
本文完結(jié)。文章來源地址http://www.zghlxwxcb.cn/news/detail-860284.html
到了這里,關(guān)于[Java]線程生命周期與線程通信的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!