线程停止继续_晓龙吊打面试官系列: 如何优雅的停止一个线程
一、什么時(shí)候我們需要中斷一個(gè)線程
在實(shí)際的開(kāi)發(fā)中,有很多場(chǎng)景需要我們中斷一個(gè)正在運(yùn)行的線程,就比如:
當(dāng)我們使用搶票軟件時(shí),其中某一個(gè)通道已經(jīng)搶到了火車票,這個(gè)時(shí)候我們就需要通知其他線程停止工作。
當(dāng)我們希望在一個(gè)限定的時(shí)間里獲得任務(wù)結(jié)果的時(shí)候,也需要在任務(wù)超時(shí)的時(shí)候關(guān)閉它
但是Java并不能像代碼塊中的break一樣干脆的退出線程運(yùn)行,Java本身提供的API方法總是讓人覺(jué)得差強(qiáng)人意,沒(méi)有辦法完美的解決我們的需求,那么我們?cè)撊绾蝺?yōu)雅的停止一個(gè)線程呢?
二、如何終止一個(gè)線程
1) 暴力停止
1. 使用stop方法停止線程
Java在停止線程的運(yùn)行方面,提供了一個(gè)stop的方法,首先我們寫段代碼,演示stop方法是如何終止線程的:
package xiao.thread.stop;public class StopThread {
private static int count;
public static void main(String[] args) throws InterruptedException {
// first :create a thread task
Thread thread = new Thread(() -> {
while (true) {
++count;
// 為了減少打印數(shù) 增加個(gè)計(jì)數(shù)判斷
//也可調(diào)用sleep方法進(jìn)行休眠
if (count % 100 == 0) {
System.out.println("此時(shí)線程依舊存活" + count);
}
}
}, "thread_task for stopThread");
thread.start();
// 如果直接終止 很有可能thread的run方法還沒(méi)有開(kāi)始執(zhí)行 所以建議讓main線程休眠一段時(shí)間來(lái)觀察效果
Thread.currentThread().sleep(1_000);
thread.stop();
}
}
首先我們可以明顯的觀察到,當(dāng)我們調(diào)用stop的時(shí)候,線程被暴力停止了。這種方式雖然簡(jiǎn)單,但是java語(yǔ)言本身并不提倡我們這么做。stop()方法呈刪除線狀態(tài),已經(jīng)表明了這個(gè)方法已經(jīng)被棄用。
2. 為什么不提倡使用stop方法
其實(shí)不提倡使用stop方法的原因很簡(jiǎn)單,因?yàn)榫€程最優(yōu)狀態(tài)的終止是只能自殺而不能被殺。具體的來(lái)說(shuō)就是:當(dāng)我們通過(guò)其他線程調(diào)用stop方法時(shí),此刻我們并不知道被殺死的線程執(zhí)行到了哪里。就比如我們?cè)谧鲆粋€(gè)為集合添加數(shù)據(jù)的操作,我們此時(shí)無(wú)法知道數(shù)據(jù)的添加進(jìn)行到了哪一步。而當(dāng)我們調(diào)用stop方法,此時(shí)被殺死的線程會(huì)立即釋放自身持有的鎖,其他線程此時(shí)就可以看到未被處理完的數(shù)據(jù),造成線程安全的問(wèn)題,破壞了對(duì)象的一致性。
2) 捕獲異常法
1. 中斷機(jī)制
中斷很好理解,它本身其實(shí)并不具備中斷的能力,只是一種協(xié)作機(jī)制。在java中并沒(méi)有對(duì)中斷進(jìn)行任何語(yǔ)法層面的實(shí)現(xiàn),只能夠靠我們利用已有的中斷標(biāo)識(shí)來(lái)記性業(yè)務(wù)處理達(dá)到中斷的目的。
2. 相關(guān)API
在實(shí)現(xiàn)中斷功能時(shí),我們常用的API主要是三個(gè)方法:
1. public void interrupt
調(diào)用此方法會(huì)將線程的中斷標(biāo)識(shí)設(shè)為true2. public boolean isInterrupt
調(diào)用此方法將會(huì)返回此刻現(xiàn)成的中斷標(biāo)識(shí)3.public static boolean interruped
該方法只能通過(guò)Thread.interrupted()調(diào)用,他會(huì)做兩個(gè)操作
第一步返回當(dāng)前線程的中斷狀態(tài)
第二部將當(dāng)前現(xiàn)成的中斷標(biāo)識(shí)設(shè)為false
3. 證實(shí)interrupt沒(méi)有停止線程的能力
首先我們舉個(gè)小例子來(lái)證明當(dāng)我們調(diào)用interrupt方法時(shí),jvm無(wú)法為我們中斷目標(biāo)線程:
public class InterruptThread {private static int count;
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("線程依舊存活:_此時(shí)計(jì)數(shù)器狀態(tài)為_(kāi)" + ++count);
try {
Thread.currentThread().sleep(1_000);
} catch (InterruptedException e) {
}
}
},"測(cè)試線程");
thread.start();
thread.interrupt();
}
}
我們看下輸出結(jié)果:
雖然我們?cè)谡{(diào)用start方法后立即調(diào)用了interrupt,但是目標(biāo)的測(cè)試線程依舊每隔一秒在控制臺(tái)打印了計(jì)數(shù)器狀態(tài),并沒(méi)有實(shí)際的中斷線程,通過(guò)這個(gè)例子我們證明了在我們調(diào)用interrupt的時(shí)候,jvm并沒(méi)有立即為我們停止目標(biāo)線程的運(yùn)行,如果我們想要在調(diào)用interrupt之后停止線程該如何做呢?
4. 異常處理法停止線程
既然我們證實(shí)了jvm確實(shí)不會(huì)主動(dòng)地替我們中斷線程,那么我們就需要利用interrupt方法中提到的中斷標(biāo)識(shí)來(lái)做一些事情。首先我們需要證明一個(gè)新的論點(diǎn):
當(dāng)阻塞方法接收到中斷信號(hào)的時(shí)候,會(huì)拋出一個(gè)InterruptedException 異常
那么我們改造下上面的代碼:
public class InterruptThread {private static int count;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getName()+"正在運(yùn)行~");
try {
Thread.currentThread().sleep(2_500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(Thread.currentThread().getName()+"接收到中斷異常");
}
}
}, "測(cè)試線程");
thread.start();
Thread.currentThread().sleep(1_000);
thread.interrupt();
}
}
運(yùn)行結(jié)果:
在這里我們可以看出,當(dāng)我們調(diào)用interrupt方法時(shí),由于線程此時(shí)也調(diào)用了可能將線程阻塞的方法sleep,因此此時(shí)目標(biāo)線程在收到我們的中斷信號(hào)的時(shí)候拋出了interruptException,但是由于while方法體里面還有代碼需要執(zhí)行,線程此時(shí)并沒(méi)有結(jié)束,這個(gè)時(shí)候我們就需要利用我們捕獲到的異常改造下代碼:
private static int count;
private static boolean mark = true ;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (mark) {
System.out.println(Thread.currentThread().getName()+"正在運(yùn)行~");
try {
Thread.currentThread().sleep(2_500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
mark = false ;
System.err.println(Thread.currentThread().getName()+"接收到中斷信號(hào)");
}
}
}, "測(cè)試線程");
thread.start();
Thread.currentThread().sleep(1_000);
thread.interrupt();
}
}
運(yùn)行結(jié)果:
可以看出程序在接收到我們的中斷信號(hào)后就沒(méi)有繼續(xù)運(yùn)行,線程停止。而上面代碼的變化其實(shí)就發(fā)生在while的判斷條件上,我們加入了一個(gè)布爾變量mark,通過(guò)捕獲異常停止線程的原理其實(shí)也很簡(jiǎn)單:我們認(rèn)為的設(shè)置一個(gè)中斷變量,將該值設(shè)置為代碼運(yùn)行的入口條件,當(dāng)我們捕獲到異常的時(shí)候,改變中斷變量的值以達(dá)到跳出循環(huán)的目的從而實(shí)現(xiàn)停止線程。
當(dāng)然,實(shí)現(xiàn)跳出循環(huán)的方式有很多,我們也可以通過(guò)return關(guān)鍵字實(shí)現(xiàn)跳出循環(huán)的效果:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getName()+"正在運(yùn)行~");
try {
Thread.currentThread().sleep(2_500);
} catch (InterruptedException e) {
e.printStackTrace();
System.err.println(Thread.currentThread().getName()+"接收到中斷信號(hào)");
return ;
}
}
}, "測(cè)試線程");
thread.start();
Thread.currentThread().sleep(1_000);
thread.interrupt();
}
}
運(yùn)行結(jié)果:
通過(guò)return也可以很好地利用異常來(lái)結(jié)束線程的運(yùn)行。
當(dāng)然,我們還有個(gè)關(guān)于interruptException異常的問(wèn)題要說(shuō)下
在實(shí)際的開(kāi)發(fā)中,我們不能不管不顧的通過(guò)拋出異常的方式來(lái)結(jié)束線程,如果你只是想記錄日志,那么此時(shí)應(yīng)該將中斷標(biāo)記重新置為false,以避免程序意外中斷
3) 通過(guò)守護(hù)線程實(shí)現(xiàn)(了解即可)
我們也可以利用守護(hù)線程的特性來(lái)結(jié)束一個(gè)線程的運(yùn)行
如果不了解守護(hù)線程的特性,可以看我之前的文章
線程的簡(jiǎn)介
下面我們先看代碼實(shí)現(xiàn):
public class DaemonThreadInterrupt {public static Thread thread;
public boolean flag = true;
public void execut(Runnable task) {
thread = new Thread(()->{
Thread thread2 = new Thread(task,"任務(wù)線程");
thread2.setDaemon(true);
thread2.start();
try {
thread2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"執(zhí)行線程");
thread.start();
}
public void stop() {
thread.interrupt();
}
public static void main(String[] args) {
DaemonThreadInterrupt daemonThreadInterrupt = new DaemonThreadInterrupt();
daemonThreadInterrupt.execut(()->{
while(true) {
System.out.println("程序運(yùn)行");
try {
thread.sleep(1_000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
daemonThreadInterrupt.stop();
}
}
實(shí)現(xiàn)原理:
在上述代碼中,我將需要實(shí)現(xiàn)的任務(wù)線程放到了一個(gè)執(zhí)行線程中,執(zhí)行線程不負(fù)責(zé)任何業(yè)務(wù)邏輯的處理,只負(fù)責(zé)啟動(dòng)任務(wù)線程。而任務(wù)線程在啟動(dòng)后,將他設(shè)為了執(zhí)行線程的守護(hù)線程。然后讓他join到執(zhí)行線程中。
然后接下來(lái)我們就利用了join方法的特性以及守護(hù)線程的特點(diǎn)設(shè)計(jì)了stop方法:
執(zhí)行join方法后,執(zhí)行線程將等待任務(wù)線程執(zhí)行完畢后才會(huì)執(zhí)行,但是當(dāng)我們調(diào)用interrupt方法時(shí),join方法接收到中斷信號(hào)就會(huì)拋出異常,此時(shí)執(zhí)行線程不在等待任務(wù)線程的運(yùn)行。重點(diǎn)來(lái)了:此時(shí)執(zhí)行線程執(zhí)行完畢,任務(wù)線程作為執(zhí)行線程的守護(hù)線程也結(jié)束了他的生命周期。
這種方式充分利用了線程的多種特性停止了一個(gè)線程,而且結(jié)束的過(guò)程可控,也可設(shè)置延時(shí)結(jié)束,適合用作定時(shí)任務(wù)線程的實(shí)現(xiàn)。
總結(jié)
以上是生活随笔為你收集整理的线程停止继续_晓龙吊打面试官系列: 如何优雅的停止一个线程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java开源对象池_JAVA 对象池
- 下一篇: 保存时间 默认_一些不起眼但又非常的实用