本篇內(nèi)容介紹了“如何使用interrupt停止線程”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)建站!專注于網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、微信小程序開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了新河免費(fèi)建站歡迎大家使用!
啟動(dòng)一個(gè)線程很簡(jiǎn)單,調(diào)用Thread類的start()方法,并在run()方法中定義需要執(zhí)行的任務(wù)就可以了,但是要正確的停止停止一個(gè)線程就需要注意了
通常情況下,我們不會(huì)手動(dòng)停止一個(gè)線程,而是允許線程運(yùn)行到結(jié)束,然后讓它自然停止。但是依然會(huì)有許多特殊的情況需要我們提前停止線程,比如:用戶突然關(guān)閉程序,或程序運(yùn)行出錯(cuò)重啟等。尤其是即將停止的線程在業(yè)務(wù)場(chǎng)景下仍然很有價(jià)值,我們就更需要正確的停止線程了。但是Java中并沒有直接提供能夠簡(jiǎn)單安全的停止線程的能力。
Java 希望程序間能夠相互通知、相互協(xié)作地管理線程,因?yàn)槿绻涣私鈱?duì)方正在做的工作,貿(mào)然強(qiáng)制停止線程就可能會(huì)造成一些安全的問題,為了避免造成問題就需要給對(duì)方一定的時(shí)間來整理收尾工作。
比如:線程正在寫入一個(gè)文件,這時(shí)收到終止信號(hào),它就需要根據(jù)自身業(yè)務(wù)判斷,是選擇立即停止,還是將整個(gè)文件寫入成功后停止,而如果選擇立即停止就可能造成數(shù)據(jù)不完整,不管是中斷命令發(fā)起者,還是接收者都不希望數(shù)據(jù)出現(xiàn)問題。
因此對(duì)于Java而言最正確的停止線程的方式是使用 interrupt。因?yàn)閕nterrupt 僅僅起到通知被停止線程的作用。而對(duì)于被停止的線程而言,它擁有完全的自主權(quán),它既可以選擇立即停止,也可以選擇一段時(shí)間后停止,也可以選擇壓根不停止。
while (!Thread.currentThread().isInterrupted() && has more work to do) {
do more work
}調(diào)用用某個(gè)線程的 interrupt() 方法之后,這個(gè)線程的中斷標(biāo)記位就會(huì)被設(shè)置成 true。每個(gè)線程都有這樣的標(biāo)記位,當(dāng)線程執(zhí)行時(shí),應(yīng)該定期檢查這個(gè)標(biāo)記位,如果標(biāo)記位被設(shè)置成 true,就說明有程序想終止該線程。在上面?zhèn)未a中可以看到,在 while 循環(huán)體判斷語句中,首先通過 Thread.currentThread().isInterrupt() 判斷線程是否被中斷,隨后檢查是否還有工作要做。
使用interrupt正確的停止線程:
public class StopThread implements Runnable{
@Override
public void run() {
int count = 0;
// 退出循環(huán)表示中斷標(biāo)志位被設(shè)置為了true(有人想停止線程)或任務(wù)完成
while(!Thread.currentThread().isInterrupted() && count < 1000){
// 中斷標(biāo)記為false(未改變)、且還有任務(wù)做則進(jìn)入循環(huán),每次都判斷一下
System.out.println("count = " + count++);
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
// 執(zhí)行當(dāng)前語句的線程進(jìn)行睡眠
Thread.sleep(5);
thread.interrupt();
}
}在 main 函數(shù)中會(huì)啟動(dòng)該子線程,然后主線程休眠 5 毫秒后立刻給該子線程發(fā)送中斷信號(hào),該子線程會(huì)檢測(cè)到中斷信號(hào),于是在還沒打印完1000個(gè)數(shù)的時(shí)候就會(huì)停下來,這種就屬于通過 interrupt 正確停止線程的情況。
Java 設(shè)計(jì)者在設(shè)計(jì)之初就考慮到了這一點(diǎn)。sleep、wait 等可以讓線程進(jìn)入阻塞的方法使線程休眠了,而處于休眠中的線程被中斷,那么線程是可以感受到中斷信號(hào)的,并且會(huì)拋出一個(gè) InterruptedException 異常,同時(shí)清除中斷信號(hào),將中斷標(biāo)記位設(shè)置成 false。這樣一來就不用擔(dān)心長(zhǎng)時(shí)間休眠中線程感受不到中斷了,因?yàn)榧幢憔€程還在休眠,仍然能夠響應(yīng)中斷通知,并拋出異常。
public class StopDuringSleep implements Runnable{
@Override
public void run() {
int num = 0;
while(!Thread.currentThread().isInterrupted() && num <= 1000){
try {
System.out.println("num = "+num++);
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopDuringSleep());
thread.start();
Thread.sleep(5);
thread.interrupt();
}
}上面代碼的邏輯為:主線程休眠 5 毫秒后,通知子線程中斷,此時(shí)子線程仍在執(zhí)行 sleep 語句,處于休眠中。執(zhí)行代碼結(jié)果發(fā)現(xiàn)程序仍在繼續(xù)執(zhí)行,但拋出了InterruptedException異常信息。可以看出線程即使在在Sleep期間也能夠感受到interrupt中斷信息,程序仍在運(yùn)行沒有正確停止的原因是我們沒有正確處理這個(gè)異常。
在實(shí)際開發(fā)中,如果我們負(fù)責(zé)編寫的方法需要被別人調(diào)用,同時(shí)我們的方法內(nèi)調(diào)用了 sleep 或者 wait 等能響應(yīng)中斷的方法時(shí),僅僅 catch 住異常是不夠的。如上面的代碼所示,catch 語句塊里代碼是空的,它并沒有進(jìn)行任何處理。假設(shè)線程執(zhí)行到這個(gè)方法,并且正在 sleep,此時(shí)有線程發(fā)送 interrupt 通知試圖中斷線程,就會(huì)立即拋出異常,并清除中斷信號(hào)。拋出的異常被 catch 語句塊捕捉。但是,捕捉到異常的 catch 沒有進(jìn)行任何處理邏輯,相當(dāng)于把中斷信號(hào)給隱藏了,這樣做是非常不合理的,所以需要正確處理異常,兩種最佳處理異常的方法如下:
void subTask2() throws InterruptedException {
Thread.sleep(1000);
}每一個(gè)方法的調(diào)用方有義務(wù)去處理異常。調(diào)用方要不使用 try/catch 并在 catch 中正確處理異常,要不將異常聲明到方法簽名中。如果每層邏輯都遵守規(guī)范,便可以將中斷信號(hào)層層傳遞到頂層,最終讓 run() 方法可以捕獲到異常。而對(duì)于 run() 方法而言,它本身沒有拋出 checkedException 的能力,只能通過 try/catch 來處理異常。層層傳遞異常的邏輯保障了異常不會(huì)被遺漏,而對(duì) run() 方法而言,就可以根據(jù)不同的業(yè)務(wù)邏輯來進(jìn)行相應(yīng)的處理。
private void reInterrupt() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}在 catch 語句中再次中斷線程。如代碼所示,需要在 catch 語句塊中調(diào)用 Thread.currentThread().interrupt() 函數(shù)。因?yàn)槿绻€程在休眠期間被中斷,那么會(huì)自動(dòng)清除中斷信號(hào)。如果這時(shí)手動(dòng)添加中斷信號(hào),中斷信號(hào)依然可以被捕捉到。這樣后續(xù)執(zhí)行的方法依然可以檢測(cè)到這里發(fā)生過中斷,可以做出相應(yīng)的處理,整個(gè)線程可以正常退出。
幾種停止線程的錯(cuò)誤方法。比如 stop(),suspend() 和 resume(),這些方法已經(jīng)被 Java 直接標(biāo)記為 @Deprecated。我們不應(yīng)該再使用它們了。
stop():會(huì)直接把線程停止,這樣就沒有給線程足夠的時(shí)間來處理想要在停止前保存數(shù)據(jù)的邏輯,任務(wù)戛然而止,會(huì)導(dǎo)致出現(xiàn)數(shù)據(jù)完整性等問題。
suspend() 和 resume() :它們的問題在于如果線程調(diào)用 suspend(),它并不會(huì)釋放鎖,就開始進(jìn)入休眠,但此時(shí)有可能仍持有鎖,這樣就容易導(dǎo)致死鎖問題,因?yàn)檫@把鎖在線程被 resume() 之前,是不會(huì)被釋放的。
例如:設(shè)線程 A 調(diào)用了 suspend() 方法讓線程 B 掛起,線程 B 進(jìn)入休眠,而線程 B 又剛好持有一把鎖,此時(shí)假設(shè)線程 A 想訪問線程 B 持有的鎖,但由于線程 B 并沒有釋放鎖就進(jìn)入休眠了,所以對(duì)于線程 A 而言,此時(shí)拿不到鎖,也會(huì)陷入阻塞,那么線程 A 和線程 B 就都無法繼續(xù)向下執(zhí)行。
public class VolatileCanStop implements Runnable{
private volatile boolean canceled = false;
@Override
public void run() {
int num = 0;
try {
while (!canceled && num <= 1000000) {
if (num % 10 == 0) {
System.out.println(num + "是10的倍數(shù)。");
}
num++;
Thread.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
VolatileCanStop volatileCanStop = new VolatileCanStop();
Thread thread = new Thread(volatileCanStop);
thread.start();
Thread.sleep(300);
volatileCanStop.canceled = true;
}
}run() 方法中進(jìn)行 while 循環(huán),在循環(huán)體中又進(jìn)行了兩層判斷,首先判斷 canceled 變量的值,canceled 變量是一個(gè)被 volatile 修飾的初始值為 false 的布爾值,當(dāng)該值變?yōu)?true 時(shí),while 跳出循環(huán),while 的第二個(gè)判斷條件是 num 值小于1000000(一百萬),在while 循環(huán)體里,只要是 10 的倍數(shù)就打印出來,然后 num++。
接下來,首先啟動(dòng)線程,然后經(jīng)過 3 秒鐘的時(shí)間,把用 volatile 修飾的布爾值的標(biāo)記位設(shè)置成 true,這樣,正在運(yùn)行的線程就會(huì)在下一次 while 循環(huán)中判斷出 canceled 的值已經(jīng)變成 true 了,這樣就不再滿足 while 的判斷條件,跳出整個(gè) while 循環(huán),線程就停止了,這種情況是演示 volatile 修飾的標(biāo)記位可以正常工作的情況,但是如果我們說某個(gè)方法是正確的,那么它應(yīng)該不僅僅是在一種情況下適用,而在其他情況下也應(yīng)該是適用的。
public class VolatileCannotStop {
/**
* 生產(chǎn)者
*/
static class Producer implements Runnable{
public volatile boolean canceled = false;
BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
try{
int num = 0;
// 停止線程信號(hào)為true,且任務(wù)執(zhí)行完畢
while(num <= 100000 && !canceled){
if (num % 50 == 0) {
// 生產(chǎn)者隊(duì)列滿了之后,會(huì)使當(dāng)前線程阻塞,阻塞后(被叫醒前)不能進(jìn)入下一次循環(huán),此時(shí)是感知不到canceled的狀態(tài)的
storage.put(num);
System.out.println(num + "是50的倍數(shù),被放到倉庫中了。");
}
num++;
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
System.out.println("生產(chǎn)者結(jié)束運(yùn)行");
}
}
}
/**
* 消費(fèi)者
*/
static class Consumer{
BlockingQueue storage;
public Consumer(BlockingQueue storage) {
this.storage = storage;
}
public boolean needMoreNums() {
if (Math.random() > 0.97) {
return false;
}
return true;
}
}
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue storage = new ArrayBlockingQueue(8);
Producer producer = new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(500);
Consumer consumer = new Consumer(storage);
while (consumer.needMoreNums()) {
System.out.println(consumer.storage.take() + "被消費(fèi)了");
Thread.sleep(100);
}
System.out.println("消費(fèi)者不需要更多數(shù)據(jù)了。");
//一旦消費(fèi)不需要更多數(shù)據(jù)了,我們應(yīng)該讓生產(chǎn)者也停下來,但是實(shí)際情況卻停不下來
producer.canceled = true;
System.out.println(producer.canceled);
}
}main 函數(shù)中,首先創(chuàng)建了生產(chǎn)者/消費(fèi)者共用的倉庫 BlockingQueue storage,倉庫容量是 8,并且建立生產(chǎn)者并將生產(chǎn)者放入線程后啟動(dòng)線程,啟動(dòng)后進(jìn)行 500 毫秒的休眠,休眠時(shí)間保障生產(chǎn)者有足夠的時(shí)間把倉庫塞滿,而倉庫達(dá)到容量后就不會(huì)再繼續(xù)往里塞,這時(shí)生產(chǎn)者會(huì)阻塞,500 毫秒后消費(fèi)者也被創(chuàng)建出來,并判斷是否需要使用更多的數(shù)字,然后每次消費(fèi)后休眠 100 毫秒。
當(dāng)消費(fèi)者不再需要數(shù)據(jù),就會(huì)將 canceled 的標(biāo)記位設(shè)置為 true,理論上此時(shí)生產(chǎn)者會(huì)跳出 while 循環(huán),打印輸出canceled的值為true,并打印輸出“生產(chǎn)者運(yùn)行結(jié)束”。
然而結(jié)果卻不是我們想象的那樣,盡管已經(jīng)把 canceled 設(shè)置成 true,但生產(chǎn)者仍然沒有停止,這是因?yàn)樵谶@種情況下,生產(chǎn)者在執(zhí)行 storage.put(num) 時(shí)發(fā)生阻塞,在它被叫醒之前是沒有辦法進(jìn)入下一次循環(huán)判斷 canceled 的值的,所以在這種情況下用 volatile 是沒有辦法讓生產(chǎn)者停下來的,相反如果用 interrupt 語句來中斷,即使生產(chǎn)者處于阻塞狀態(tài),仍然能夠感受到中斷信號(hào),并做響應(yīng)處理。
“如何使用interrupt停止線程”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
分享文章:如何使用interrupt停止線程
文章來源:http://chinadenli.net/article34/gogdse.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供自適應(yīng)網(wǎng)站、靜態(tài)網(wǎng)站、服務(wù)器托管、軟件開發(fā)、關(guān)鍵詞優(yōu)化、定制網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
移動(dòng)網(wǎng)站建設(shè)知識(shí)