Java多線程的理解

之前對(duì)操作系統(tǒng)和組成原理回顧了一下,又稍微理解了操作系統(tǒng)是怎樣調(diào)度應(yīng)用程序進(jìn)程,以及CPU在這個(gè)過(guò)程中起到了什么作用。

在這里先以進(jìn)程為引子,引出線程。

進(jìn)程是操作系統(tǒng)能夠看到的最小單位,也是就說(shuō),操作系統(tǒng)調(diào)度進(jìn)程,把時(shí)間片分配給某個(gè)進(jìn)程。如果這個(gè)進(jìn)程在執(zhí)行的時(shí)候阻塞了,比如他要執(zhí)行I/O操作,那么他會(huì)執(zhí)行系統(tǒng)調(diào)用,后面操作系統(tǒng)的代碼會(huì)以內(nèi)核態(tài)在CPU上執(zhí)行,那么用戶進(jìn)程就處于阻塞狀態(tài)了。

所謂阻塞,在用戶看來(lái)就是"不可響應(yīng)了",比如正在執(zhí)行I/O操作,用戶怎么點(diǎn)這個(gè)界面都沒(méi)反應(yīng)。對(duì)于一個(gè)應(yīng)用來(lái)說(shuō),他可不想這么快就陷入阻塞態(tài),所以,他希望在執(zhí)行I/O操作的時(shí)候也不會(huì)陷入阻塞操作,那么在時(shí)間片內(nèi),該進(jìn)程還是可以繼續(xù)執(zhí)行代碼,同時(shí)呢,CPU也在內(nèi)核態(tài)幫他執(zhí)行I/O操作,這就需要線程的幫助了。

對(duì)進(jìn)程來(lái)說(shuō),I/O操作必須是異步的,CPU執(zhí)行了進(jìn)程的I/O操作后,還必須執(zhí)行后面的指令。

線程是由JVM實(shí)現(xiàn)的,對(duì)應(yīng)著CPU看到的就是跳轉(zhuǎn)指令,也就是另外一條執(zhí)行流。用java代碼開(kāi)啟一個(gè)新的線程,在底層就是開(kāi)啟一條新的執(zhí)行流。

那么,現(xiàn)在應(yīng)該怎么理解線程呢?對(duì)進(jìn)程來(lái)說(shuō),他相當(dāng)于一個(gè)異步任務(wù),對(duì)CPU來(lái)說(shuō),他是另外一條指令流。

計(jì)算機(jī)是一層層實(shí)現(xiàn)的,上層的高級(jí)抽象的概念都是由底層實(shí)現(xiàn)的,如果CPU不能執(zhí)行跳轉(zhuǎn)指令,那么上層可能也就沒(méi)有多線程的概念了。

線程的創(chuàng)建

線程在java中是一個(gè)Thread類(lèi),當(dāng)Thread類(lèi)被啟動(dòng)時(shí),它執(zhí)行Runnable接口中的run()方法。如果Thread類(lèi)沒(méi)有對(duì)應(yīng)的Runnable對(duì)象,那么什么也不做。

線程的結(jié)束

創(chuàng)建線程的目的是為了執(zhí)行異步任務(wù),如果任務(wù)執(zhí)行完了,或者中途取消了,那么線程應(yīng)該停止執(zhí)行任務(wù),畢竟,創(chuàng)建Thread類(lèi)是需要耗費(fèi)資源的,而資源是有限的。

java并沒(méi)有一種方法能夠立刻是線程結(jié)束,而線程能不能中斷,是有所執(zhí)行的方法決定的,如sleep()方法,也就是該方法每次都會(huì)檢查標(biāo)志位,判斷線程是否被中斷,如果被中斷,則拋出中斷異常。

所以,中斷線程實(shí)際上是給線程設(shè)置一個(gè)中斷標(biāo)志位,而中斷方法回去檢查這個(gè)標(biāo)志位,如果該方法響應(yīng)了該方法,則拋出中斷異常,然后結(jié)束線程的執(zhí)行;而有的中斷方法,可能太過(guò)重要,可以忽視中斷標(biāo)志位,繼續(xù)執(zhí)行。這是對(duì)中斷整體的理解,后面就是各個(gè)中斷方法的不同處理了。

1.run()方法正常結(jié)束

當(dāng)run()方法正常結(jié)束,那么Thread后面會(huì)被Java虛擬機(jī)回收。

2.手動(dòng)設(shè)置“標(biāo)志位”中斷非阻塞狀態(tài)的進(jìn)程

 public void run() {
        while (!cancelled){
                //do something          
          }
 }
 
 問(wèn)題:如果run方法中有阻塞方法,一旦陷入阻塞,如果條件得不到滿足,那么"標(biāo)志位"將永遠(yuǎn)不會(huì)得到檢查,那么線程也就無(wú)法結(jié)束。

3.使用interrupt()中斷非阻塞線程

如果run()方法中有阻塞方法,如wait,sleep方法,即使線程是阻塞狀態(tài),也會(huì)檢查阻塞標(biāo)志位,檢測(cè)線程是否被阻塞,一旦阻塞標(biāo)志位是true,則拋出InterruptedException異常。

interrupt()方法
一旦調(diào)用了該方法,相當(dāng)于給線程設(shè)置了一個(gè)中斷標(biāo)志位,對(duì)于中斷方法(如sleep),他們會(huì)主動(dòng)檢查這個(gè)標(biāo)志位;而如果沒(méi)有阻塞方法,我們需要手動(dòng)去檢查。
isInterrupted()會(huì)檢查線程的中斷標(biāo)志位

while (!Thread.currentThread().isInterrupted()) {
        //do something

}

4.使用interrupt()中斷 阻塞線程

所謂阻塞線程,這里指線程執(zhí)行了阻塞方法,如sleep()。   
當(dāng)interrupt()方法被中斷后,會(huì)設(shè)置該線程的中斷標(biāo)志位,如果線程此時(shí)處于阻塞狀態(tài)(wait,sleep方法),則interrupt狀態(tài)被清除,拋出InterruptedException異常;如果線程正常執(zhí)行,將會(huì)設(shè)置中斷位,線程正常執(zhí)行,知道它執(zhí)行了阻塞方法,拋出InterruptedException異常。

public void run() {   //注意try的位置
    while (!Thread.currentThread().isInterrupted()) {
        System.out.println("Thread running...");
        try {
            /*
             * 如果線程阻塞,將不會(huì)去檢查中斷信號(hào)量stop變量,所 以thread.interrupt()
             * 會(huì)使阻塞線程從阻塞的地方拋出異常,讓阻塞線程從阻塞狀態(tài)逃離出來(lái),并
             * 進(jìn)行異常塊進(jìn)行 相應(yīng)的處理
             */
            Thread.sleep(1000);// 線程阻塞,如果線程收到中斷操作信號(hào)將拋出異常
        } catch (InterruptedException e) {
            System.out.println("Thread interrupted...");
            /*
             * 如果線程在調(diào)用 Object.wait()方法,或者該類(lèi)的 join() 、sleep()方法
             * 過(guò)程中受阻,則其中斷狀態(tài)將被清除
             */
            System.out.println(this.isInterrupted());// false

            //中不中斷由自己決定,如果需要真真中斷線程,則需要重新設(shè)置中斷位,如果
            //不需要,則不用調(diào)
            //如果這里不調(diào)用interrupt()方法,代碼會(huì)接著執(zhí)行
            Thread.currentThread().interrupt();
        }
    }
    System.out.println("Thread exiting under request...");
}

中斷I/O操作

在網(wǎng)絡(luò)操作中,對(duì)于阻塞的判定是需要考慮很多的,比如你的請(qǐng)求時(shí)間,你的響應(yīng)時(shí)間,如果請(qǐng)求時(shí)間很長(zhǎng),而網(wǎng)絡(luò)信號(hào)可能不好,用戶處于等待狀態(tài),那么此時(shí)是不能算作I/O阻塞的,同樣的道理可以用于服務(wù)器響應(yīng)。

對(duì)應(yīng)到j(luò)ava語(yǔ)言中,實(shí)現(xiàn)了InterruptibleChannel接口的通道是可中斷的,也就是說(shuō),實(shí)現(xiàn)了InterruptibleChannel的方法會(huì)主動(dòng)去檢測(cè)中斷標(biāo)志位,其邏輯和sleep()方法一樣,只是拋出了ClosedByInterruptException異常。

不拋出InterruptedException異常的I/O阻塞方法

InputStream的read()方法

API里對(duì)read()方法的解釋是,如果已經(jīng)到達(dá)流末尾而沒(méi)有可用的字節(jié),則返回-1。在輸入數(shù)據(jù)可用、檢測(cè)到流末尾或者拋出異常前,此方法一直阻塞。而Socket在關(guān)閉之前是不會(huì)關(guān)閉流的,所以read()方法就不知道什么時(shí)候到達(dá)流末尾,就會(huì)一直阻塞。

解決辦法:服務(wù)器在發(fā)送完數(shù)據(jù)后關(guān)閉輸出流
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 又來(lái)到了一個(gè)老生常談的問(wèn)題,應(yīng)用層軟件開(kāi)發(fā)的程序員要不要了解和深入學(xué)習(xí)操作系統(tǒng)呢? 今天就這個(gè)問(wèn)題開(kāi)始,來(lái)談?wù)劜?..
    tangsl閱讀 4,317評(píng)論 0 23
  • 第三章 Java內(nèi)存模型 3.1 Java內(nèi)存模型的基礎(chǔ) 通信在共享內(nèi)存的模型里,通過(guò)寫(xiě)-讀內(nèi)存中的公共狀態(tài)進(jìn)行隱...
    澤毛閱讀 4,500評(píng)論 2 21
  • 一、多線程 說(shuō)明下線程的狀態(tài) java中的線程一共有 5 種狀態(tài)。 NEW:這種情況指的是,通過(guò) New 關(guān)鍵字創(chuàng)...
    Java旅行者閱讀 4,858評(píng)論 0 44
  • 當(dāng)你看一個(gè)人而不去評(píng)判他的美和丑、好和壞、賢與惡時(shí)——當(dāng)你不帶評(píng)判的看進(jìn)對(duì)方的眼睛,突然的一種相會(huì)發(fā)生了,能量融合...
    明瑩閱讀 437評(píng)論 0 0
  • 文丨顧十 目錄丨【此生,我只愛(ài)你目錄】 上一章丨第一章 歸來(lái) 第二章 始于心動(dòng) “氣死我了,這是什么人吶,第一次見(jiàn)...
    顧十閱讀 384評(píng)論 0 5

友情鏈接更多精彩內(nèi)容