Java多次啟動(dòng)一個(gè)線程究竟會(huì)發(fā)生什么?程序到底會(huì)不會(huì)崩?大部分程序員理解錯(cuò)誤??!
今天給大家分享的面試題是:一個(gè)線程調(diào)用兩次start()方法會(huì)出現(xiàn)什么現(xiàn)象?這道面試題是一道關(guān)于多線程的基礎(chǔ)面試題,很多小伙伴對(duì)這個(gè)面試題不太了解,其實(shí),如果你看過(guò)JDK中關(guān)于Thread類的源碼,那這道面試題對(duì)你來(lái)說(shuō)就能過(guò)輕松應(yīng)對(duì)了。
手寫(xiě)RPC框架視頻錄制中,發(fā)布地址:https://space.bilibili.com/517638832/channel/collectiondetail?sid=4186280
今天,我們就一起來(lái)聊聊這道面試題,以及面試官問(wèn)這道題的面試分析拓展知識(shí)。
優(yōu)質(zhì)回答
Java 的線程是不允許啟動(dòng)兩次的,第二次調(diào)用必然會(huì)拋出 IllegalThreadStateException,這是一種運(yùn)行時(shí)異常,表示非法的線程狀態(tài)異常。
關(guān)于線程生命周期的不同狀態(tài),在 Java 5 以后,線程狀態(tài)被明確定義在其公共內(nèi)部枚舉類型 java.lang.Thread.State 中,分別是:
- 新建(NEW),表示線程被創(chuàng)建出來(lái)還沒(méi)真正啟動(dòng)的狀態(tài),可以認(rèn)為它是個(gè) Java 內(nèi)部狀態(tài)。
- 就緒(RUNNABLE),表示該線程已經(jīng)在 JVM 中執(zhí)行,當(dāng)然由于執(zhí)行需要計(jì)算資源,它可能是正在運(yùn)行,也可能還在等待系統(tǒng)分配給它 CPU 片段,在就緒隊(duì)列里面排隊(duì)。
- 在其他一些分析中,會(huì)額外區(qū)分一種狀態(tài) RUNNING,但是從 Java API 的角度,并不能表示出來(lái)。
- 阻塞(BLOCKED),這個(gè)狀態(tài)和我們前面兩講介紹的同步非常相關(guān),阻塞表示線程在等待 Monitor lock。比如,線程試圖通過(guò) synchronized 去獲取某個(gè)鎖,但是其他線程已經(jīng)獨(dú)占了,那么當(dāng)前線程就會(huì)處于阻塞狀態(tài)。
- 等待(WAITING),表示正在等待其他線程采取某些操作。一個(gè)常見(jiàn)的場(chǎng)景是類似生產(chǎn)者消費(fèi)者模式,發(fā)現(xiàn)任務(wù)條件尚未滿足,就讓當(dāng)前消費(fèi)者線程等待(wait),另外的生產(chǎn)者線程去準(zhǔn)備任務(wù)數(shù)據(jù),然后通過(guò)類似 notify 等動(dòng)作,通知消費(fèi)線程可以繼續(xù)工作了。Thread.join() 也會(huì)令線程進(jìn)入等待狀態(tài)。
- 計(jì)時(shí)等待(TIMED_WAIT),其進(jìn)入條件和等待狀態(tài)類似,但是調(diào)用的是存在超時(shí)條件的方法,比如 wait 或 join 等方法的指定超時(shí)版本,如下面示例:
public final native void wait(long timeout) throws InterruptedException;
- 終止(TERMINATED),不管是意外退出還是正常執(zhí)行結(jié)束,線程已經(jīng)完成使命,終止運(yùn)行,也有人把這個(gè)狀態(tài)叫作死亡。
在第二次調(diào)用 start() 方法的時(shí)候,線程可能處于終止或者其他(非 NEW)狀態(tài),但是不論如何,都是不可以再次啟動(dòng)的。
面試分析
今天的面試題看似簡(jiǎn)單,實(shí)則是對(duì)面試者基礎(chǔ)知識(shí)的考察,很多大廠在面試時(shí),很重視面試者對(duì)基礎(chǔ)知識(shí)的掌握程度,往往這些基礎(chǔ)知識(shí)是大家最容易忽視的。
知識(shí)拓展
線程的通用生命周期
線程在運(yùn)行的過(guò)程中,會(huì)經(jīng)歷幾種狀態(tài)之間的轉(zhuǎn)換,而線程在這幾種狀態(tài)之間的轉(zhuǎn)換流程,基本上就構(gòu)成了線程的生命周期。本小節(jié),就簡(jiǎn)單介紹下線程的通用生命周期。
線程的通用生命周期總體上可以分為五種狀態(tài),分別為:初始狀態(tài)、可運(yùn)行狀態(tài)、運(yùn)行狀態(tài)、休眠狀態(tài)和終止?fàn)顟B(tài)
圖片
可以看出,線程的通用生命周期可以分為初始狀態(tài)、可運(yùn)行狀態(tài)、運(yùn)行狀態(tài)、休眠狀態(tài)和終止?fàn)顟B(tài)五種狀態(tài)。
(1)初始狀態(tài):初始狀態(tài)比較特殊,這種狀態(tài)屬于編程語(yǔ)言層面特有的狀態(tài),處于初始狀態(tài)的線程只是線程在編程語(yǔ)言層面被創(chuàng)建了,但是在操作系統(tǒng)層面,并沒(méi)有真正的創(chuàng)建線程。
(2)可運(yùn)行狀態(tài):在操作系統(tǒng)層面,線程被真正的創(chuàng)建,并且可以分配CPU執(zhí)行。
(3)運(yùn)行狀態(tài):處于運(yùn)行狀態(tài)的線程已經(jīng)獲取到CPU資源,正在運(yùn)行。
(4)休眠狀態(tài):線程正在等待某個(gè)事件的發(fā)生(例如等待I/O事件的完成),或者調(diào)用了一個(gè)阻塞的API正處于阻塞狀態(tài)(例如以阻塞的方式讀寫(xiě)文件等),此時(shí)的線程處于休眠狀態(tài)。
(5)終止?fàn)顟B(tài):線程正常運(yùn)行結(jié)束或者出現(xiàn)異常,就會(huì)進(jìn)入終止?fàn)顟B(tài)。
線程的通用生命周期中各狀態(tài)之間的轉(zhuǎn)換關(guān)系如下所示。
(1)初始狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài):處于初始狀態(tài)的線程,實(shí)際上并沒(méi)有在操作系統(tǒng)中創(chuàng)建對(duì)應(yīng)的線程,當(dāng)在操作系統(tǒng)中創(chuàng)建了對(duì)應(yīng)的線程時(shí),此時(shí)線程就會(huì)從初始狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài)。
(2)可運(yùn)行狀態(tài)轉(zhuǎn)換成運(yùn)行狀態(tài):如果操作系統(tǒng)中存在空閑的CPU資源,則操作系統(tǒng)會(huì)將空閑的CPU資源分配給一個(gè)處于可運(yùn)行狀態(tài)的線程,處于可運(yùn)行狀態(tài)的線程獲得CPU資源后就會(huì)轉(zhuǎn)換成運(yùn)行狀態(tài)。也就是說(shuō),處于可運(yùn)行狀態(tài)的線程,被操作系統(tǒng)調(diào)度獲取到CPU資源后,就會(huì)從可運(yùn)行狀態(tài)轉(zhuǎn)換成運(yùn)行狀態(tài)。
(3)運(yùn)行狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài):當(dāng)正在運(yùn)行的線程CPU時(shí)間片用完時(shí),就會(huì)從運(yùn)行狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài)。
(4)運(yùn)行狀態(tài)轉(zhuǎn)換成休眠狀態(tài):處于運(yùn)行狀態(tài)的線程如果等待某個(gè)事件的發(fā)生(例如,等待I/O事件的完成),或者調(diào)用了一個(gè)阻塞的API(例如,以阻塞的方式讀寫(xiě)文件等),此時(shí)處于運(yùn)行狀態(tài)的線程就會(huì)釋放CPU的資源,從運(yùn)行狀態(tài)轉(zhuǎn)換成休眠狀態(tài)。
(5)休眠狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài):如果處于休眠狀態(tài)的線程等待的事件已經(jīng)發(fā)生(例如,等待的I/O事件已經(jīng)完成),或者調(diào)用的阻塞API已經(jīng)完成操作(例如,以阻塞的方式讀寫(xiě)文件已經(jīng)完成),則線程就會(huì)從休眠狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài)。
(6)運(yùn)行狀態(tài)轉(zhuǎn)換成終止?fàn)顟B(tài):處于運(yùn)行狀態(tài)的線程正常運(yùn)行結(jié)束,或者出現(xiàn)異常,就會(huì)從運(yùn)行狀態(tài)轉(zhuǎn)換成終止?fàn)顟B(tài)。處于終止?fàn)顟B(tài)的線程,不會(huì)再轉(zhuǎn)換成其他的狀態(tài),線程的生命周期也就結(jié)束了。
注意:在線程的通用生命周期中,只有處于運(yùn)行狀態(tài)的線程可以直接轉(zhuǎn)換成終止?fàn)顟B(tài)和休眠狀態(tài),處于其他狀態(tài)的線程都不能直接轉(zhuǎn)換成終止?fàn)顟B(tài)和休眠狀態(tài)。處于休眠狀態(tài)的線程只能直接轉(zhuǎn)換成可運(yùn)行狀態(tài),不能直接轉(zhuǎn)換成其他狀態(tài)。
Java中線程的生命周期
在Java中,線程的生命周期主要包括:初始化狀態(tài)、可運(yùn)行狀態(tài)、阻塞狀態(tài)、等待狀態(tài)、超時(shí)等待狀態(tài)和終止?fàn)顟B(tài)。其中,可運(yùn)行狀態(tài)又包括運(yùn)行狀態(tài)和就緒狀態(tài)。
圖片
可以看出,在Java的線程生命周期中,總體上包含初始化狀態(tài)、可運(yùn)行狀態(tài)、等待狀態(tài)、超時(shí)等待狀態(tài)、阻塞狀態(tài)和終止?fàn)顟B(tài)六種狀態(tài)。
(1)初始化狀態(tài):線程在Java中被創(chuàng)建,但是還沒(méi)有調(diào)用線程對(duì)象的start()方法,也就是說(shuō),還沒(méi)有創(chuàng)建操作系統(tǒng)層面對(duì)應(yīng)的線程。
(2)可運(yùn)行狀態(tài):Java線程生命周期中的可運(yùn)行狀態(tài),包含操作系統(tǒng)中線程的運(yùn)行狀態(tài)和就緒狀態(tài)。
(3)等待狀態(tài):處于等待狀態(tài)的線程需要等待其他線程對(duì)當(dāng)前線程進(jìn)行通知或者中斷等操作,從而進(jìn)入下一個(gè)線程狀態(tài)。
(4)超時(shí)等待狀態(tài):處于超時(shí)等待狀態(tài)的線程需要在指定的時(shí)間內(nèi),等待其他線程對(duì)當(dāng)前線程進(jìn)行通知或者中斷等操作。如果在指定的時(shí)間內(nèi),存在其他線程對(duì)當(dāng)前線程進(jìn)行通知或者中斷等操作,則當(dāng)前線程進(jìn)入下一個(gè)狀態(tài)。否則超過(guò)指定的時(shí)間,當(dāng)前線程也會(huì)進(jìn)入下一個(gè)狀態(tài)。
(5)阻塞狀態(tài):處于阻塞狀態(tài)的線程需要等待其他線程釋放鎖,或者等待進(jìn)入synchronized臨界區(qū)。
(6)終止?fàn)顟B(tài):表示當(dāng)前線程執(zhí)行完畢,包括正常執(zhí)行結(jié)束和異常退出。
Java的線程生命周期中的可運(yùn)行狀態(tài),涵蓋了運(yùn)行狀態(tài)和就緒狀態(tài)。
(1)運(yùn)行狀態(tài):對(duì)應(yīng)操作系統(tǒng)中的運(yùn)行狀態(tài)。
(2)就緒狀態(tài):對(duì)應(yīng)操作系統(tǒng)中的就緒狀態(tài)。
在Java的線程生命周期中,各狀態(tài)之間的轉(zhuǎn)換關(guān)系如下所示。
1.初始化狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài)的場(chǎng)景
在Java層面,調(diào)用線程對(duì)象的start()方法,會(huì)在操作系統(tǒng)層面創(chuàng)建對(duì)應(yīng)的線程,此時(shí),線程的狀態(tài)就會(huì)從初始化狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài)。
2.可運(yùn)行狀態(tài)與等待狀態(tài)互相轉(zhuǎn)換的場(chǎng)景一
(1)線程a調(diào)用synchronized(obj)獲取到對(duì)象鎖后,調(diào)用obj.wait()方法時(shí),線程a的狀態(tài)會(huì)從可運(yùn)行狀態(tài)轉(zhuǎn)換成等待狀態(tài)。
(2)在滿足(1)時(shí),此時(shí)線程b調(diào)用synchronized(obj)獲取到對(duì)象鎖后,調(diào)用obj.notify()方法、obj.notifyAll()方法、a.interrupt()方法,此時(shí)會(huì)有兩種情況,如下所示。
l 線程a競(jìng)爭(zhēng)鎖成功,則線程a會(huì)由等待狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài)。
l 線程a競(jìng)爭(zhēng)鎖失敗,則線程a會(huì)由等待狀態(tài)轉(zhuǎn)換成阻塞狀態(tài)。
3.可運(yùn)行狀態(tài)與等待狀態(tài)互相轉(zhuǎn)換的場(chǎng)景二
(1)線程a調(diào)用線程b的join()方法時(shí),線程a會(huì)由可運(yùn)行狀態(tài)轉(zhuǎn)換成等待狀態(tài)。
(2)在滿足(1)時(shí),線程b運(yùn)行結(jié)束,或者調(diào)用了線程a的interrupt()方法,則線程a會(huì)從等待狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài)。
4.可運(yùn)行狀態(tài)與等待狀態(tài)互相轉(zhuǎn)換的場(chǎng)景三
(1)線程a調(diào)用LockSupport.park()方法時(shí),線程a會(huì)從可運(yùn)行狀態(tài)轉(zhuǎn)換成等待狀態(tài)。
(2)在滿足(1)時(shí),其他線程調(diào)用LockSupport.unpark(a),或者調(diào)用線程a的interrupt()方法,線程a會(huì)從等待狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài)。
5.可運(yùn)行狀態(tài)與超時(shí)等待狀態(tài)互相轉(zhuǎn)換的場(chǎng)景一
(1)線程a調(diào)用synchronized(obj)獲取到對(duì)象鎖后,調(diào)用obj.wait(long n)方法,則線程a會(huì)從可運(yùn)行狀態(tài)轉(zhuǎn)換成超時(shí)等待狀態(tài)。
(2)在滿足(1)時(shí),線程a的等待時(shí)間超過(guò)了n毫秒,或者線程b調(diào)用synchronized(obj)獲取到對(duì)象鎖后,調(diào)用obj.notify()方法、obj.notifyAll()方法、a.interrupt()方法,此時(shí)會(huì)有兩種情況,如下所示。
l 線程a競(jìng)爭(zhēng)鎖成功,則線程a會(huì)由超時(shí)等待狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài)。
l 線程a競(jìng)爭(zhēng)鎖失敗,則線程a會(huì)由超時(shí)等待狀態(tài)轉(zhuǎn)換成阻塞狀態(tài)。
6.可運(yùn)行狀態(tài)與超時(shí)等待狀態(tài)互相轉(zhuǎn)換的場(chǎng)景二
(1)線程a調(diào)用Thread.sleep(long n)方法,則線程a會(huì)從可運(yùn)行狀態(tài)轉(zhuǎn)換成超時(shí)等待狀態(tài)。
(2)在滿足(1)時(shí),線程a的等待時(shí)間超過(guò)n毫秒,則線程a會(huì)從超時(shí)等待狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài)。
7.可運(yùn)行狀態(tài)與超時(shí)等待狀態(tài)互相轉(zhuǎn)換的場(chǎng)景三
(1)線程a調(diào)用了線程b的join(long n)方法時(shí),線程a會(huì)從可運(yùn)行狀態(tài)轉(zhuǎn)換成超時(shí)等待狀態(tài)。
(2)在滿足(1)時(shí),線程a的等待時(shí)間超過(guò)n毫秒,或者線程b運(yùn)行結(jié)束,或者調(diào)用了線程a的interrupt()方法,線程a會(huì)從超時(shí)等待狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài)。
8.可運(yùn)行狀態(tài)與超時(shí)等待狀態(tài)互相轉(zhuǎn)換的場(chǎng)景四
(1)線程a調(diào)用Locksupport.parkNanos(long nacos)方法,或者調(diào)用LockSupport.parkUntil(long millis)方法時(shí),線程a會(huì)從可運(yùn)行狀態(tài)轉(zhuǎn)換成超時(shí)等待狀態(tài)。
(2)在滿足(1)時(shí),其他線程調(diào)用LockSupport.unpark(a),或者調(diào)用線程a的interrupt()方法,或者線程a等待超時(shí),則線程a會(huì)從超時(shí)等待狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài)。
9.可運(yùn)行狀態(tài)與阻塞狀態(tài)互相轉(zhuǎn)換的場(chǎng)景一
(1)線程a與線程b共同爭(zhēng)搶同一個(gè)悲觀鎖,線程b爭(zhēng)搶成功,則線程a會(huì)從可運(yùn)行狀態(tài)轉(zhuǎn)換成阻塞狀態(tài)。
(2)在滿足(1)時(shí),線程b釋放鎖時(shí),線程a獲取到鎖,則線程a會(huì)從阻塞狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài)。
10.可運(yùn)行狀態(tài)與阻塞狀態(tài)互相轉(zhuǎn)換的場(chǎng)景二
(1)線程a調(diào)用synchronized(obj)獲取對(duì)象鎖時(shí),競(jìng)爭(zhēng)失敗,則線程a會(huì)從可運(yùn)行狀態(tài)轉(zhuǎn)換成阻塞狀態(tài)。
(2)在滿足(1)時(shí),調(diào)用synchronized(obj)獲取對(duì)象鎖時(shí)競(jìng)爭(zhēng)成功的線程,執(zhí)行同步代碼塊完畢,就會(huì)喚醒所有阻塞在obj對(duì)象上的線程,這些被喚醒的線程會(huì)重新競(jìng)爭(zhēng),如果線程a競(jìng)爭(zhēng)成功,則線程a會(huì)從阻塞狀態(tài)轉(zhuǎn)換成可運(yùn)行狀態(tài)。如果線程a競(jìng)爭(zhēng)失敗,則線程a繼續(xù)保持阻塞狀態(tài)。
11.可運(yùn)行狀態(tài)轉(zhuǎn)換成終止?fàn)顟B(tài)的場(chǎng)景
線程a正常執(zhí)行結(jié)束,或者由于某種原因異常退出,線程a就會(huì)從可運(yùn)行狀態(tài)轉(zhuǎn)換成終止?fàn)顟B(tài)。
如果一個(gè)線程轉(zhuǎn)換成終止?fàn)顟B(tài),那么就標(biāo)注著這個(gè)線程已經(jīng)運(yùn)行結(jié)束,不能再次轉(zhuǎn)換成其他狀態(tài)。