Java的線程阻塞、中斷及優(yōu)雅退出方法詳解
線程阻塞
一個(gè)線程進(jìn)入阻塞狀態(tài)的原因可能如下(已排除Deprecated方法):
sleep()
sleep()使當(dāng)前線程進(jìn)入停滯狀態(tài)(阻塞當(dāng)前線程),讓出CUP的使用、目的是不讓當(dāng)前線程獨(dú)自霸占該進(jìn)程所獲的CPU資源,以留一定時(shí)間給其他線程執(zhí)行的機(jī)會(huì);
當(dāng)在一個(gè)Synchronized塊中調(diào)用Sleep()方法是,線程雖然休眠了,但是對(duì)象鎖并沒(méi)有被釋放,其他線程無(wú)法訪問(wèn)這個(gè)對(duì)象(即使睡著也持有對(duì)象鎖)。
wait()
調(diào)用wait()/1.5中的condition.await()使線程掛起,直到線程獲取notify()/notifyAll()消息,(或者在Java SE5中java.util.concurrent類(lèi)庫(kù)中等價(jià)的signal()/signalAll()消息),線程才會(huì)進(jìn)入就緒狀態(tài);
wait()調(diào)用會(huì)釋放當(dāng)前對(duì)象鎖(monitor),這樣其他線程可以繼續(xù)進(jìn)入對(duì)象的同步方法。
另外,調(diào)用join()也會(huì)導(dǎo)致線程阻塞,因?yàn)樵创a中join()就是通過(guò)wait()實(shí)現(xiàn)的;
等待I/O;
class Demo3 implements Runnable throws InterruptedException{ private InputStream in; public void run(){ in.read(); } }
無(wú)法持有鎖進(jìn)入同步代碼
進(jìn)入同步代碼前無(wú)法獲取鎖,比如試圖調(diào)用synchronized方法,或者顯示鎖對(duì)象的上鎖行為ReentrantLock.lock(),而對(duì)應(yīng)鎖已被其他線程獲取的情況下都將導(dǎo)致線程進(jìn)入阻塞狀態(tài);
注意:yield()并不會(huì)導(dǎo)致線程轉(zhuǎn)到等待/睡眠/阻塞狀態(tài)。在大多數(shù)情況下,yield()將導(dǎo)致線程從運(yùn)行狀態(tài)轉(zhuǎn)到可運(yùn)行狀態(tài),但有可能沒(méi)有效果。
線程中斷
線程中斷可以在線程內(nèi)部設(shè)置一個(gè)中斷標(biāo)識(shí),同時(shí)讓處于(可中斷)阻塞的線程拋出InterruptedException中斷異常,使線程跳出阻塞狀態(tài)。相比其他語(yǔ)言,Java線程中斷比較特殊,經(jīng)常會(huì)引起開(kāi)發(fā)人員的誤解。因?yàn)橹袛嗦?tīng)起來(lái)高深復(fù)雜,實(shí)質(zhì)原理上非常簡(jiǎn)單。
中斷原理
Java中斷機(jī)制是一種協(xié)作機(jī)制,也就是說(shuō)通過(guò)中斷并不能直接終止另一個(gè)線程,而需要被中斷的線程自己處理中斷。這好比是家里的父母叮囑在外的子女要注意身體,但子女是否注意身體,怎么注意身體則完全取決于自己。
Java中斷模型也是這么簡(jiǎn)單,每個(gè)線程對(duì)象里都有一個(gè)boolean類(lèi)型的標(biāo)識(shí)(不一定就要是Thread類(lèi)的字段,實(shí)際上也的確不是,這幾個(gè)方法最終都是通過(guò)native方法來(lái)完成的),代表著是否有中斷請(qǐng)求(該請(qǐng)求可以來(lái)自所有線程,包括被中斷的線程本身)。例如,當(dāng)線程t1想中斷線程t2,只需要在線程t1中將線程t2對(duì)象的中斷標(biāo)識(shí)置為true,然后線程2可以選擇在合適的時(shí)候處理該中斷請(qǐng)求,甚至可以不理會(huì)該請(qǐng)求,就像這個(gè)線程沒(méi)有被中斷一樣。
中斷相關(guān)的方法
方法 | 解釋 |
public static boolean interrupted() | 測(cè)試當(dāng)前線程是否已經(jīng)中斷。線程的中斷狀態(tài) 由該方法清除。換句話說(shuō),如果連續(xù)兩次調(diào)用該方法,則第二次調(diào)用將返回 false(在第一次調(diào)用已清除了其中斷狀態(tài)之后,且第二次調(diào)用檢驗(yàn)完中斷狀態(tài)前,當(dāng)前線程再次中斷的情況除外)。 |
public boolean isInterrupted() | 測(cè)試線程是否已經(jīng)中斷。線程的中斷狀態(tài)不受該方法的影響。 |
public void interrupt() | 中斷線程,設(shè)置中斷標(biāo)識(shí)為為true。 |
其中,interrupt方法是唯一能將中斷狀態(tài)設(shè)置為true的方法。靜態(tài)方法interrupted會(huì)將當(dāng)前線程的中斷狀態(tài)清除,但這個(gè)方法的命名極不直觀,很容易造成誤解,需要特別注意。
此外,類(lèi)庫(kù)中的有些類(lèi)的方法也可能會(huì)調(diào)用中斷,如FutureTask中的cancel方法,如果傳入的參數(shù)為true,它將會(huì)在正在運(yùn)行異步任務(wù)的線程上調(diào)用interrupt方法,如果正在執(zhí)行的異步任務(wù)中的代碼沒(méi)有對(duì)中斷做出響應(yīng),那么cancel方法中的參數(shù)將不會(huì)起到什么效果;
ExecutorService exec = Executors.newCachedThreadPool(); Futrue<?> f = exec.submit(new TaskThread()); f.interrupt();
又如ThreadPoolExecutor中的shutdownNow方法會(huì)遍歷線程池中的工作線程并調(diào)用線程的interrupt方法來(lái)中斷線程,所以如果工作線程中正在執(zhí)行的任務(wù)沒(méi)有對(duì)中斷做出響應(yīng),任務(wù)將一直執(zhí)行直到正常結(jié)束。
ExecutorService exec = Executors.newCachedThreadPool(); for(int i=0;i<5;i++) exec.execute(new TaskThread()) exec.shutdownNow();
中斷的處理
中斷一個(gè)線程只是為了引起該線程的注意,被中斷線程可以決定如何應(yīng)對(duì)中斷。某些線程非常重要,以至于它們應(yīng)該不理會(huì)中斷,而是在處理完拋出的異常之后繼續(xù)執(zhí)行,但是更普遍的情況是,一個(gè)線程將把中斷看作一個(gè)終止請(qǐng)求,這種線程的run方法遵循如下形式:
public void run() { try { ... /* * 不管循環(huán)里是否調(diào)用過(guò)線程阻塞的方法如sleep、join、wait,這里還是需要加上 * !Thread.currentThread().isInterrupted()條件,雖然拋出異常后退出了循環(huán),顯 * 得用阻塞的情況下是多余的,但如果調(diào)用了阻塞方法但沒(méi)有阻塞時(shí),這樣會(huì)更安全、更及時(shí)。 */ while (!Thread.currentThread().isInterrupted()&& more work to do) { do more work } } catch (InterruptedException e) { //線程在wait或sleep期間被中斷了 } finally { //線程結(jié)束前做一些清理工作 } }
上面是while循環(huán)在try塊里,如果try在while循環(huán)里時(shí),因該在catch塊里重新設(shè)置一下中斷標(biāo)示,因?yàn)閽伋鯥nterruptedException異常后,中斷標(biāo)示位會(huì)自動(dòng)清除,此時(shí)應(yīng)該這樣:
public void run() { while (!Thread.currentThread().isInterrupted()&& more work to do) { try { ... sleep(delay); //wait(delay); } catch (InterruptedException e) { Thread.currentThread().interrupt(); //重新設(shè)置中斷標(biāo)示 } } }
可中斷阻塞
對(duì)于處于sleep,join等操作的線程,如果被調(diào)用interrupt()后,會(huì)拋出InterruptedException,然后線程的中斷標(biāo)志位會(huì)由true重置為false,因?yàn)榫€程為了處理異常已經(jīng)重新處于就緒狀態(tài)。
不可中斷的操作,包括進(jìn)入synchronized段以及Lock.lock(),inputSteam.read()等,調(diào)用interrupt()對(duì)于這幾個(gè)問(wèn)題無(wú)效,因?yàn)樗鼈兌疾粧伋鲋袛喈惓!H绻貌坏劫Y源,它們會(huì)無(wú)限期阻塞下去。
對(duì)于Lock.lock(),可以改用Lock.lockInterruptibly(),可被中斷的加鎖操作,它可以拋出中斷異常。等同于等待時(shí)間無(wú)限長(zhǎng)的Lock.tryLock(long time, TimeUnit unit)。
對(duì)于inputStream等資源,有些(實(shí)現(xiàn)了interruptibleChannel接口)可以通過(guò)close()方法將資源關(guān)閉,對(duì)應(yīng)的阻塞也會(huì)被放開(kāi)。
但是,你可能正使用Java1.0之前就存在的傳統(tǒng)的I/O,Thread.interrupt()將不起作用,因?yàn)榫€程將不會(huì)退出被阻塞狀態(tài)。
很幸運(yùn),Java平臺(tái)為這種情形提供了一項(xiàng)解決方案,即調(diào)用阻塞該線程的套接字的close()方法。在這種情形下,如果線程被I/O操作阻塞,當(dāng)調(diào)用該套接字的close方法時(shí),該線程在調(diào)用accept地方法將接收到一個(gè)SocketException(SocketException為IOException的子異常)異常,這與使用interrupt()方法引起一個(gè)InterruptedException異常被拋出非常相似。
java.nio類(lèi)庫(kù)提供了更加人性化的I/O中斷,被阻塞的nio通道會(huì)自動(dòng)地響應(yīng)中斷,不需要關(guān)閉底層資源;
線程優(yōu)雅退出
一般情況下,線程退出可以使用while循環(huán)判斷共享變量條件的方式,當(dāng)線程內(nèi)有阻塞操作時(shí),可能導(dǎo)致線程無(wú)法運(yùn)行到條件判斷的地方而導(dǎo)致一直阻塞下去,這個(gè)時(shí)候就需要中斷來(lái)幫助線程脫離阻塞。因此比較優(yōu)雅的退出線程方式是結(jié)合共享變量和中斷。
thread = new Thread(new Runnable() { @Override public void run() { /* * 在這里為一個(gè)循環(huán),條件是判斷線程的中斷標(biāo)志位是否中斷 */ while (flag&&(!Thread.currentThread().isInterrupted())) { try { Log.i("tag","線程運(yùn)行中"+Thread.currentThread().getId()); // 每執(zhí)行一次暫停40毫秒 //當(dāng)sleep方法拋出InterruptedException 中斷狀態(tài)也會(huì)被清掉 Thread.sleep(40); } catch (InterruptedException e) { e.printStackTrace(); //如果拋出異常則再次設(shè)置中斷請(qǐng)求 Thread.currentThread().interrupt(); } } } }); thread.start();
到此這篇關(guān)于Java的線程阻塞、中斷及優(yōu)雅退出方法詳解的文章就介紹到這了,更多相關(guān)Java線程阻塞、中斷及優(yōu)雅退出內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot項(xiàng)目整合注冊(cè)功能模塊開(kāi)發(fā)實(shí)戰(zhàn)
這篇文章主要介紹了springboot項(xiàng)目整合注冊(cè)功能模塊開(kāi)發(fā)實(shí)戰(zhàn),在用戶的注冊(cè)是首先需要查詢當(dāng)前的用戶名是否存在,如果存在則不能進(jìn)行注冊(cè),相當(dāng)于一個(gè)查詢語(yǔ)句,本文通過(guò)實(shí)例代碼詳細(xì)講解,需要的朋友可以參考下2022-11-11使用jd-gui反編譯修改jar包里的.class并重新生成新jar問(wèn)題
這篇文章主要介紹了使用jd-gui反編譯修改jar包里的.class并重新生成新jar問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05Core Java 簡(jiǎn)單談?wù)凥ashSet(推薦)
下面小編就為大家?guī)?lái)一篇Core Java 簡(jiǎn)單談?wù)凥ashSet(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09自定義starter引發(fā)的線上事故記錄復(fù)盤(pán)
這篇文章主要為大家介紹了自定義starter引發(fā)的線上事故記錄復(fù)盤(pán),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05Java for each實(shí)現(xiàn)機(jī)制代碼原理解析
這篇文章主要介紹了Java for each實(shí)現(xiàn)機(jī)制代碼原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06SpringBoot?AOP?Redis實(shí)現(xiàn)延時(shí)雙刪功能實(shí)戰(zhàn)
本文主要介紹了SpringBoot?AOP?Redis實(shí)現(xiàn)延時(shí)雙刪功能實(shí)戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08SpringBoot中Session的使用及說(shuō)明
這篇文章主要介紹了SpringBoot中Session的使用及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06SpringBoot2底層注解@Configuration配置類(lèi)詳解
這篇文章主要為大家介紹了SpringBoot2底層注解@Configuration配置類(lèi)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05