某種意義上,這也是控制反轉(zhuǎn)(IoC)機制的一種體現(xiàn),cpu不用反復(fù)去詢問硬盤,這也是所謂的“好萊塢原則”—Don’t call us, we will call you.好萊塢的經(jīng)紀人經(jīng)常對演員們說:“別打電話給我,(有戲時)我們會打電話給你?!?/section>在這里,硬盤與 cpu 的互動機制也是類似,硬盤對 cpu 說:”別老來問我 IO 做完了沒有,完了我自然會通知你的“當然了,cpu 還是要不斷地檢查中斷,就好比演員們也要時刻注意接聽電話,不過這總好過不斷主動去詢問,畢竟絕大多數(shù)的詢問都將是徒勞的。cpu 會收到一個比如說來自硬盤的中斷信號,并進入中斷處理例程,手頭正在執(zhí)行的線程因此被打斷,回到 ready 隊列。而先前因 I/O 而waiting 的線程隨著 I/O 的完成也再次回到 ready 隊列,這時 cpu 可能會選擇它來執(zhí)行。另一方面,所謂的時間分片輪轉(zhuǎn)本質(zhì)上也是由一個定時器定時中斷來驅(qū)動的,可以使線程從 running 回到 ready 狀態(tài):比如設(shè)置一個10ms 的倒計時,時間一到就發(fā)一個中斷,好像大限已到一樣,然后重置倒計時,如此循環(huán)。與 cpu 正打得火熱的線程可能不情愿聽到這一中斷信號,因為它意味著這一次與 cpu 纏綿的時間又要到頭了......奴為出來難,何日君再來?
現(xiàn)在我們再看一下 Java 中定義的線程狀態(tài),嘿,它也有 BLOCKED(阻塞),也有 WAITING(等待),甚至它還更細,還有TIMED_WAITING:現(xiàn)在問題來了,進行阻塞式 I/O 操作時,Java 的線程狀態(tài)究竟是什么?是 BLOCKED?還是 WAITING?可能你已經(jīng)猜到,既然放到 RUNNABLE 這一主題下討論,其實狀態(tài)還是 RUNNABLE。我們也可以通過一些測試來驗證這一點:@Test
public void testInBlockedIOState() throws InterruptedException {
Scanner in = new Scanner(System.in);
// 創(chuàng)建一個名為“輸入輸出”的線程t
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
// 命令行中的阻塞讀
String input = in.nextLine();
System.out.println(input);
} catch (Exception e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(in);
}
}
}, "輸入輸出"); // 線程的名字
// 啟動
t.start();
// 確保run已經(jīng)得到執(zhí)行
Thread.sleep(100);
// 狀態(tài)為RUNNABLE
assertThat(t.getState()).isEqualTo(Thread.State.RUNNABLE);
}
在最后的語句上加一斷點,監(jiān)控上也反映了這一點:網(wǎng)絡(luò)阻塞時同理,比如socket.accept,我們說這是一個“阻塞式(blocked)”式方法,但線程狀態(tài)還是 RUNNABLE。@Test
public void testBlockedSocketState() throws Exception {
Thread serverThread = new Thread(new Runnable() {
@Override
public void run() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(10086);
while (true) {
// 阻塞的accept方法
Socket socket = serverSocket.accept();
// TODO
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}, "socket線程"); // 線程的名字
serverThread.start();
// 確保run已經(jīng)得到執(zhí)行
Thread.sleep(500);
// 狀態(tài)為RUNNABLE
assertThat(serverThread.getState()).isEqualTo(Thread.State.RUNNABLE);
}
當然,Java 很早就引入了所謂 nio(新的IO)包,至于用 nio 時線程狀態(tài)究竟是怎樣的,這里就不再一一具體去分析了。至少我們看到了,進行傳統(tǒng)上的 IO 操作時,口語上我們也會說“阻塞”,但這個“阻塞”與線程的 BLOCKED 狀態(tài)是兩碼事!
如何看待RUNNABLE狀態(tài)?
虛擬機是騎在你操作系統(tǒng)上面的,身下的操作系統(tǒng)是作為某種資源為滿足虛擬機的需求而存在的:當進行阻塞式的 IO 操作時,或許底層的操作系統(tǒng)線程確實處在阻塞狀態(tài),但我們關(guān)心的是 JVM 的線程狀態(tài)。JVM 并不關(guān)心底層的實現(xiàn)細節(jié),什么時間分片也好,什么 IO 時就要切換也好,它并不關(guān)心。
前面說到,“處于 runnable 狀態(tài)下的線程正在*?Java 虛擬機中執(zhí)行,但它可能正在等待*來自于操作系統(tǒng)的其它資源,比如處理器?!?/section>JVM 把那些都視作資源,cpu 也好,硬盤,網(wǎng)卡也罷,有東西在為線程服務(wù),它就認為線程在“執(zhí)行”。你用嘴,用手,還是用什么鳥東西來滿足它的需求,它并不關(guān)心~
處于 IO 阻塞,只是說 cpu 不執(zhí)行線程了,但網(wǎng)卡可能還在監(jiān)聽呀,雖然可能暫時沒有收到數(shù)據(jù):就好比前臺或保安坐在他們的位置上,可能沒有接待什么人,但你能說他們沒在工作嗎?
所以 JVM 認為線程還在執(zhí)行。而操作系統(tǒng)的線程狀態(tài)是圍繞著 cpu 這一核心去述說的,這與 JVM 的側(cè)重點是有所不同的。前面我們也強調(diào)了“Java 線程狀態(tài)的改變通常只與自身顯式引入的機制有關(guān)”,如果 JVM 中的線程狀態(tài)發(fā)生改變了,通常是自身機制引發(fā)的。比如 synchronize 機制有可能讓線程進入BLOCKED 狀態(tài),sleep,wait等方法則可能讓其進入 WATING 之類的狀態(tài)。
它與傳統(tǒng)的線程狀態(tài)的對應(yīng)可以如下來看:RUNNABLE 狀態(tài)對應(yīng)了傳統(tǒng)的 ready, running 以及部分的 waiting 狀態(tài)。重磅!碼農(nóng)突圍-技術(shù)交流群已成立掃碼可添加碼農(nóng)突圍助手,可申請加入碼農(nóng)突圍大群和細分方向群,細分方向已涵蓋:Java、Python、機器學習、大數(shù)據(jù)、人工智能等群。一定要備注:開發(fā)方向+地點+學校/公司+昵稱(如Java開發(fā)+上海+拼夕夕+猴子),根據(jù)格式備注,可更快被通過且邀請進群
推薦閱讀