之前對(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)閉輸出流