0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

為什么Java線程沒有Running狀態(tài)?

Android編程精選 ? 來源:開源博客 ? 作者:國棟 ? 2021-06-17 17:36 ? 次閱讀

什么是 RUNNABLE?

與傳統(tǒng)的ready狀態(tài)的區(qū)別

與傳統(tǒng)的running狀態(tài)的區(qū)別

當(dāng)I/O阻塞時(shí)

如何看待RUNNABLE狀態(tài)?

Java虛擬機(jī)層面所暴露給我們的狀態(tài),與操作系統(tǒng)底層的線程狀態(tài)是兩個(gè)不同層面的事。

具體而言,這里說的 Java 線程狀態(tài)均來自于 Thread 類下的 State 這一內(nèi)部枚舉類中所定義的狀態(tài):

什么是 RUNNABLE?

直接看它的 Javadoc 中的說明:

一個(gè)在 JVM 中執(zhí)行的線程處于這一狀態(tài)中。(A thread executing in the Java virtual machine is in this state.)。

那么 runnable 與圖中的 ready 與 running 區(qū)別在哪呢?

與傳統(tǒng)的ready狀態(tài)的區(qū)別

更具體點(diǎn),javadoc 中是這樣說的:

處于 runnable 狀態(tài)下的線程正在 Java 虛擬機(jī)中執(zhí)行,但它可能正在等待來自于操作系統(tǒng)的其它資源,比如處理器。

A thread in the runnable state is executing in the Java virtual machine but it may be waiting forother resources from the operating system such as processor.

顯然,runnable 狀態(tài)實(shí)質(zhì)上是包括了 ready 狀態(tài)的。

甚至還可能有包括上圖中的 waiting 狀態(tài)的部分細(xì)分狀態(tài),在后面我們將會(huì)看到這一點(diǎn)

與傳統(tǒng)的running狀態(tài)的區(qū)別

有人常覺得 Java 線程狀態(tài)中還少了個(gè) running 狀態(tài),這其實(shí)是把兩個(gè)不同層面的狀態(tài)混淆了。

對(duì) Java 線程狀態(tài)而言,不存在所謂的running 狀態(tài),它的 runnable 狀態(tài)包含了 running 狀態(tài)。

我們可能會(huì)問,為何 JVM 中沒有去區(qū)分這兩種狀態(tài)呢?

現(xiàn)在的時(shí)分(time-sharing)多任務(wù)(multi-task)操作系統(tǒng)架構(gòu)通常都是用所謂的“時(shí)間分片(time quantum or time slice)”方式進(jìn)行搶占式(preemptive)輪轉(zhuǎn)調(diào)度(round-robin式)。

更復(fù)雜的可能還會(huì)加入優(yōu)先級(jí)(priority)的機(jī)制。

這個(gè)時(shí)間分片通常是很小的,一個(gè)線程一次最多只能在 cpu 上運(yùn)行比如10-20ms 的時(shí)間(此時(shí)處于 running 狀態(tài)),也即大概只有0.01秒這一量級(jí),時(shí)間片用后就要被切換下來放入調(diào)度隊(duì)列的末尾等待再次調(diào)度。(也即回到 ready 狀態(tài))

注:如果期間進(jìn)行了 I/O 的操作還會(huì)導(dǎo)致提前釋放時(shí)間分片,并進(jìn)入等待隊(duì)列。

又或者是時(shí)間分片沒有用完就被搶占,這時(shí)也是回到 ready 狀態(tài)。

這一切換的過程稱為線程的上下文切換(context switch),當(dāng)然 cpu 不是簡(jiǎn)單地把線程踢開就完了,還需要把被相應(yīng)的執(zhí)行狀態(tài)保存到內(nèi)存中以便后續(xù)的恢復(fù)執(zhí)行。

顯然,10-20ms 對(duì)人而言是很快的,

不計(jì)切換開銷(每次在1ms 以內(nèi)),相當(dāng)于1秒內(nèi)有50-100次切換。

事實(shí)上時(shí)間片經(jīng)常沒用完,線程就因?yàn)楦鞣N原因被中斷,實(shí)際發(fā)生的切換次數(shù)還會(huì)更多。

也這正是單核 *CPU 上實(shí)現(xiàn)所謂的“并發(fā)*(concurrent)”的基本原理,但其實(shí)是快速切換所帶來的假象

這有點(diǎn)類似一個(gè)手腳非常快的雜耍演員可以讓好多個(gè)球同時(shí)在空中運(yùn)轉(zhuǎn)那般。

時(shí)間分片也是可配置的,如果不追求在多個(gè)線程間很快的響應(yīng),也可以把這個(gè)時(shí)間配置得大一點(diǎn),以減少切換帶來的開銷。

如果是多核CPU,才有可能實(shí)現(xiàn)真正意義上的并發(fā),這種情況通常也叫并行(pararell),不過你可能也會(huì)看到這兩詞會(huì)被混著用,這里就不去糾結(jié)它們的區(qū)別了。

通常,Java的線程狀態(tài)是服務(wù)于監(jiān)控的,如果線程切換得是如此之快,那么區(qū)分 ready 與 running 就沒什么太大意義了。

當(dāng)你看到監(jiān)控上顯示是 running 時(shí),對(duì)應(yīng)的線程可能早就被切換下去了,甚至又再次地切換了上來,也許你只能看到 ready 與 running 兩個(gè)狀態(tài)在快速地閃爍。

當(dāng)然,對(duì)于精確的性能評(píng)估而言,獲得準(zhǔn)確的 running 時(shí)間是有必要的。

現(xiàn)今主流的 JVM 實(shí)現(xiàn)都把 Java 線程一一映射到操作系統(tǒng)底層的線程上,把調(diào)度委托給了操作系統(tǒng),我們?cè)谔摂M機(jī)層面看到的狀態(tài)實(shí)質(zhì)是對(duì)底層狀態(tài)的映射及包裝。

JVM 本身沒有做什么實(shí)質(zhì)的調(diào)度,把底層的 ready 及 running 狀態(tài)映射上來也沒多大意義,因此,統(tǒng)一成為runnable 狀態(tài)是不錯(cuò)的選擇。

我們將看到,Java 線程狀態(tài)的改變通常只與自身顯式引入的機(jī)制有關(guān)。

當(dāng)I/O阻塞時(shí)

我們知道傳統(tǒng)的I/O都是阻塞式(blocked)的,原因是I/O操作比起cpu來實(shí)在是太慢了,可能差到好幾個(gè)數(shù)量級(jí)都說不定。

如果讓 cpu 去等I/O 的操作,很可能時(shí)間片都用完了,I/O 操作還沒完成呢,不管怎樣,它會(huì)導(dǎo)致 cpu 的利用率極低。

所以,解決辦法就是:一旦線程中執(zhí)行到 I/O 有關(guān)的代碼,相應(yīng)線程立馬被切走,然后調(diào)度 ready 隊(duì)列中另一個(gè)線程來運(yùn)行。

這時(shí)執(zhí)行了 I/O 的線程就不再運(yùn)行,即所謂的被阻塞了。它也不會(huì)被放到調(diào)度隊(duì)列中去,因?yàn)楹芸赡茉俅握{(diào)度到它時(shí),I/O 可能仍沒有完成。

線程會(huì)被放到所謂的等待隊(duì)列中,處于上圖中的 waiting 狀態(tài):

當(dāng)然了,我們所謂阻塞只是指這段時(shí)間 cpu 暫時(shí)不會(huì)理它了,但另一個(gè)部件比如硬盤則在努力地為它服務(wù)。

cpu 與硬盤間是并發(fā)的,如果把線程視作為一個(gè) job,這一 job 由 cpu 與硬盤交替協(xié)作完成,當(dāng)在 cpu 上是 waiting 時(shí),在硬盤上卻處于 running,只是我們?cè)诓僮飨到y(tǒng)層面討論線程狀態(tài)時(shí)通常是圍繞著 cpu 這一中心去述說的。

而當(dāng) I/O 完成時(shí),則用一種叫中斷(interrupt)的機(jī)制來通知 cpu:

也即所謂的“中斷驅(qū)動(dòng)(interrupt-driven)”,現(xiàn)代操作系統(tǒng)基本都采用這一機(jī)制。

某種意義上,這也是控制反轉(zhuǎn)(IoC)機(jī)制的一種體現(xiàn),cpu不用反復(fù)去詢問硬盤,這也是所謂的“好萊塢原則”—Don’t call us, we will call you.好萊塢的經(jīng)紀(jì)人經(jīng)常對(duì)演員們說:“別打電話給我,(有戲時(shí))我們會(huì)打電話給你?!?/p>

在這里,硬盤與 cpu 的互動(dòng)機(jī)制也是類似,硬盤對(duì) cpu 說:”別老來問我 IO 做完了沒有,完了我自然會(huì)通知你的“

當(dāng)然了,cpu 還是要不斷地檢查中斷,就好比演員們也要時(shí)刻注意接聽電話,不過這總好過不斷主動(dòng)去詢問,畢竟絕大多數(shù)的詢問都將是徒勞的。

cpu 會(huì)收到一個(gè)比如說來自硬盤的中斷信號(hào),并進(jìn)入中斷處理例程,手頭正在執(zhí)行的線程因此被打斷,回到 ready 隊(duì)列。而先前因 I/O 而waiting 的線程隨著 I/O 的完成也再次回到 ready 隊(duì)列,這時(shí) cpu 可能會(huì)選擇它來執(zhí)行。

另一方面,所謂的時(shí)間分片輪轉(zhuǎn)本質(zhì)上也是由一個(gè)定時(shí)器定時(shí)中斷來驅(qū)動(dòng)的,可以使線程從 running 回到 ready 狀態(tài):

比如設(shè)置一個(gè)10ms 的倒計(jì)時(shí),時(shí)間一到就發(fā)一個(gè)中斷,好像大限已到一樣,然后重置倒計(jì)時(shí),如此循環(huán)。

與 cpu 正打得火熱的線程可能不情愿聽到這一中斷信號(hào),因?yàn)樗馕吨@一次與 cpu 纏綿的時(shí)間又要到頭了……奴為出來難,何日君再來?

現(xiàn)在我們?cè)倏匆幌?Java 中定義的線程狀態(tài),嘿,它也有 BLOCKED(阻塞),也有 WAITING(等待),甚至它還更細(xì),還有TIMED_WAITING:

現(xiàn)在問題來了,進(jìn)行阻塞式 I/O 操作時(shí),Java 的線程狀態(tài)究竟是什么?是 BLOCKED?還是 WAITING?

可能你已經(jīng)猜到,既然放到 RUNNABLE 這一主題下討論,其實(shí)狀態(tài)還是 RUNNABLE。我們也可以通過一些測(cè)試來驗(yàn)證這一點(diǎn):

@Test public void testInBlockedIOState() throws InterruptedException

{ Scanner in = new Scanner(System.in); // 創(chuàng)建一個(gè)名為“輸入輸出”的線程

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);

} } }, “輸入輸出”); // 線程的名字 // 啟動(dòng) t.start(); // 確保run已經(jīng)得到執(zhí)行 Thread.sleep(100); // 狀態(tài)為RUNNABLE assertThat(t.getState()).isEqualTo(Thread.State.RUNNABLE); }

在最后的語句上加一斷點(diǎn),監(jiān)控上也反映了這一點(diǎn):

網(wǎng)絡(luò)阻塞時(shí)同理,比如socket.accept,我們說這是一個(gè)“阻塞式(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); }:

當(dāng)然,Java 很早就引入了所謂 nio(新的IO)包,至于用 nio 時(shí)線程狀態(tài)究竟是怎樣的,這里就不再一一具體去分析了。

至少我們看到了,進(jìn)行傳統(tǒng)上的 IO 操作時(shí),口語上我們也會(huì)說“阻塞”,但這個(gè)“阻塞”與線程的 BLOCKED 狀態(tài)是兩碼事!

如何看待RUNNABLE狀態(tài)?

首先還是前面說的,注意分清兩個(gè)層面:

虛擬機(jī)是騎在你操作系統(tǒng)上面的,身下的操作系統(tǒng)是作為某種資源為滿足虛擬機(jī)的需求而存在的:

當(dāng)進(jìn)行阻塞式的 IO 操作時(shí),或許底層的操作系統(tǒng)線程確實(shí)處在阻塞狀態(tài),但我們關(guān)心的是 JVM 的線程狀態(tài)。

JVM 并不關(guān)心底層的實(shí)現(xiàn)細(xì)節(jié),什么時(shí)間分片也好,什么 IO 時(shí)就要切換也好,它并不關(guān)心。

前面說到,“處于 runnable 狀態(tài)下的線程正在* Java 虛擬機(jī)中執(zhí)行,但它可能正在等待*來自于操作系統(tǒng)的其它資源,比如處理器?!?/p>

JVM 把那些都視作資源,cpu 也好,硬盤,網(wǎng)卡也罷,有東西在為線程服務(wù),它就認(rèn)為線程在“執(zhí)行”。

處于 IO 阻塞,只是說 cpu 不執(zhí)行線程了,但網(wǎng)卡可能還在監(jiān)聽呀,雖然可能暫時(shí)沒有收到數(shù)據(jù):

就好比前臺(tái)或保安坐在他們的位置上,可能沒有接待什么人,但你能說他們沒在工作嗎?

所以 JVM 認(rèn)為線程還在執(zhí)行。而操作系統(tǒng)的線程狀態(tài)是圍繞著 cpu 這一核心去述說的,這與 JVM 的側(cè)重點(diǎn)是有所不同的。

前面我們也強(qiáng)調(diào)了“Java 線程狀態(tài)的改變通常只與自身顯式引入的機(jī)制有關(guān)”,如果 JVM 中的線程狀態(tài)發(fā)生改變了,通常是自身機(jī)制引發(fā)的。

比如 synchronize 機(jī)制有可能讓線程進(jìn)入BLOCKED 狀態(tài),sleep,wait等方法則可能讓其進(jìn)入 WATING 之類的狀態(tài)。

RUNNABLE 狀態(tài)對(duì)應(yīng)了傳統(tǒng)的 ready, running 以及部分的 waiting 狀態(tài)。

原文標(biāo)題:為什么 Java 線程沒有 Running 狀態(tài)?一下被問懵!

文章出處:【微信公眾號(hào):Android編程精選】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

責(zé)任編輯:haq

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2952

    瀏覽量

    104484
  • 虛擬機(jī)
    +關(guān)注

    關(guān)注

    1

    文章

    904

    瀏覽量

    28018

原文標(biāo)題:為什么 Java 線程沒有 Running 狀態(tài)?一下被問懵!

文章出處:【微信號(hào):AndroidPush,微信公眾號(hào):Android編程精選】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    探索虛擬線程:原理與實(shí)現(xiàn)

    虛擬線程的引入與優(yōu)勢(shì) 在Loom項(xiàng)目之前,Java虛擬機(jī)(JVM)中的線程是通過java.lang.Thread類型來實(shí)現(xiàn)的,這些線程被稱為
    的頭像 發(fā)表于 06-24 11:35 ?244次閱讀
    探索虛擬<b class='flag-5'>線程</b>:原理與實(shí)現(xiàn)

    動(dòng)態(tài)線程池思想學(xué)習(xí)及實(shí)踐

    相關(guān)文檔 美團(tuán)線程池實(shí)踐:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html 線程池思想解析:https
    的頭像 發(fā)表于 06-13 15:43 ?1117次閱讀
    動(dòng)態(tài)<b class='flag-5'>線程</b>池思想學(xué)習(xí)及實(shí)踐

    java實(shí)現(xiàn)多線程的幾種方式

    Java實(shí)現(xiàn)多線程的幾種方式 多線程是指程序中包含了兩個(gè)或以上的線程,每個(gè)線程都可以并行執(zhí)行不同的任務(wù)或操作。
    的頭像 發(fā)表于 03-14 16:55 ?550次閱讀

    關(guān)于FX3使用4個(gè)線程進(jìn)行FPGA到USB的數(shù)據(jù)傳輸-狀態(tài)機(jī)設(shè)置的問題求解

    我現(xiàn)在使用FX3來實(shí)現(xiàn)FPGA到上位機(jī)的數(shù)據(jù)傳輸,在使用2個(gè)線程用類似于UVC實(shí)例中的乒乓機(jī)制傳輸時(shí)沒有問題。我看到GPIF II最多支持4個(gè)線程,所以想試一下4個(gè)線程的數(shù)據(jù)傳輸,我使
    發(fā)表于 02-27 06:40

    java虛擬機(jī)內(nèi)存包括遠(yuǎn)空間內(nèi)存嗎

    詳細(xì)介紹JVM內(nèi)存的各個(gè)部分及其作用。 Java堆(Heap) Java堆是JVM管理的最大一塊內(nèi)存區(qū)域,用于存放Java對(duì)象實(shí)例。在堆中分配內(nèi)存由垃圾收集器(GC)自動(dòng)進(jìn)行,主要負(fù)責(zé)對(duì)象的創(chuàng)建和回收。堆空間是
    的頭像 發(fā)表于 12-05 14:15 ?366次閱讀

    java中cpu占用過高如何分析

    Java中CPU占用過高是一種常見的問題,需要通過多種方法進(jìn)行分析和解決。本文將詳細(xì)介紹Java中CPU占用過高的原因以及如何進(jìn)行分析和優(yōu)化。 一、CPU占用過高的原因 線程過多:Java
    的頭像 發(fā)表于 12-05 11:15 ?8890次閱讀

    java死鎖產(chǎn)生的條件

    Java死鎖是指多個(gè)線程因?yàn)榛ハ嗟却龑?duì)方釋放資源而無法繼續(xù)執(zhí)行的情況。當(dāng)線程處于死鎖狀態(tài)時(shí),程序會(huì)無限期地等待資源,無法繼續(xù)執(zhí)行下去,從而導(dǎo)致整個(gè)系統(tǒng)的停滯。要理解并避免
    的頭像 發(fā)表于 12-04 13:42 ?425次閱讀

    核心線程數(shù)和最大線程數(shù)怎么設(shè)置

    核心線程數(shù)和最大線程數(shù)是Java線程池中重要的參數(shù),用來控制線程池中線程的數(shù)量和行為。正確地設(shè)置
    的頭像 發(fā)表于 12-01 13:50 ?8499次閱讀

    Spring Boot 3.2支持虛擬線程和原生鏡像

    Spring Boot 3.2 前幾日發(fā)布,讓我們用 Java 21、GraalVM 和虛擬線程來嘗試一下。
    的頭像 發(fā)表于 11-30 16:22 ?671次閱讀

    如何查看java程序的內(nèi)存分布

    。 程序計(jì)數(shù)器: 程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,它的作用是指示當(dāng)前線程所執(zhí)行的字節(jié)碼指令的行號(hào)。在多線程環(huán)境下,每個(gè)線程都有一個(gè)獨(dú)立的程序計(jì)數(shù)器,用于記錄當(dāng)前線程執(zhí)行的字節(jié)碼指令。
    的頭像 發(fā)表于 11-23 14:47 ?987次閱讀

    java內(nèi)存溢出排查方法

    模型。Java內(nèi)存模型分為線程棧、堆、方法區(qū)(Java 8之前稱為永久代,Java 8后稱為元空間)和本地方法棧
    的頭像 發(fā)表于 11-23 14:46 ?3036次閱讀

    線程如何保證數(shù)據(jù)的同步

    。本文將詳細(xì)介紹多線程數(shù)據(jù)同步的概念、問題、以及常見的解決方案。 一、多線程數(shù)據(jù)同步概念 在多線程編程中,數(shù)據(jù)同步指的是通過某種機(jī)制來確保多個(gè)線程對(duì)共享數(shù)據(jù)的操作按照一定的順序和規(guī)則進(jìn)
    的頭像 發(fā)表于 11-17 14:22 ?1108次閱讀

    阻塞狀態(tài)和等待狀態(tài)的區(qū)別

    阻塞狀態(tài)和等待狀態(tài)是計(jì)算機(jī)領(lǐng)域中常用的術(shù)語,用來描述進(jìn)程或線程狀態(tài)。盡管這兩個(gè)狀態(tài)在表面上有些相似,但它們有著本質(zhì)上的區(qū)別。本文將詳盡、詳
    的頭像 發(fā)表于 11-17 11:33 ?3545次閱讀

    就緒狀態(tài)和等待狀態(tài)的區(qū)別

    就緒狀態(tài)和等待狀態(tài)是計(jì)算機(jī)領(lǐng)域中一對(duì)常用的術(shù)語,用于描述進(jìn)程或線程在執(zhí)行時(shí)的不同狀況。下面我將詳細(xì)解釋就緒狀態(tài)和等待狀態(tài)的區(qū)別。 就緒
    的頭像 發(fā)表于 11-17 11:29 ?2463次閱讀

    如何查看一個(gè)線程的ID

    1.什么是線程? linux內(nèi)核中是沒有線程這個(gè)概念的,而是輕量級(jí)進(jìn)程的概念:LWP。一般我們所說的線程概念是C庫當(dāng)中的概念。 1.1線程是怎樣描述的?
    的頭像 發(fā)表于 11-13 14:38 ?1257次閱讀
    如何查看一個(gè)<b class='flag-5'>線程</b>的ID