如何正确的停止线程?
如何正确停止线程?
使用interrupt来通知,而不是强制
通常线程会在什么情况下停止
1.所有代码执行完毕
2.有异常出现
正确停止方法:interrupt
1.通常情况下如何停止?
在run()方法中的while语句中加入!Thread.currentThread().isInterrupted()去监测线程是否已经被中断了。
package threadcoreknowledge.stopthreads;
/**
* 描述: run方法内没有sleep或wait方法时,停止线程
*/
public class RightWayStopThreadWithoutSleep implements Runnable{
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
thread.start();
Thread.sleep(100);
thread.interrupt();
}
@Override
public void run() {
int num = 0;
while(!Thread.currentThread().isInterrupted()&&num<Integer.MAX_VALUE/2){
if(num%10000==0){
System.out.println(num+"是10000的倍数。");
}
num++;
}
System.out.println("任务运行结束了");
}
}
2. 线程被阻塞如何停止?
会抛出sleep interrupted异常,响应中断信号。
package threadcoreknowledge.stopthreads;
/**
* 描述: 带有sleep()的中断线程的写法
*/
public class RightWayStopThreadWithSleep {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = ()->{
int num = 0;
try{
while(num <= 300 && !Thread.currentThread().isInterrupted()){
if(num%100==0){
System.out.println(num+"是100的倍数");
}
num++;
}
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
};
Thread thread= new Thread(runnable);
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
运行结果:
E:\.jdks\corretto-1.8.0_252\bin\java.exe "-javaagent:E:\IntelliJ IDEA 2019.3.5\lib\idea_rt.jar=57184:E:\IntelliJ IDEA 2019.3.5\bin" -Dfile.encoding=UTF-8 -classpath E:\.jdks\corretto-1.8.0_252\jre\lib\charsets.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\access-bridge-64.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\cldrdata.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\dnsns.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\jaccess.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\jfxrt.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\localedata.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\nashorn.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\sunec.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\sunjce_provider.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\sunmscapi.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\sunpkcs11.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\zipfs.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\jce.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\jfxswt.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\jsse.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\management-agent.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\resources.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\rt.jar;E:\workspace\thread-study\out\production\demo01 threadcoreknowledge.stopthreads.RightWayStopThreadWithSleep
0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at threadcoreknowledge.stopthreads.RightWayStopThreadWithSleep.lambda$main$0(RightWayStopThreadWithSleep.java:14)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
3. 如果线程在每次迭代后都阻塞如何停止?
不需要再加入!Thread.currentThread().isInterrupted()判断条件,因为在sleep()过程中会响应中断。
package threadcoreknowledge.stopthreads;
/**
* 描述: 如果在执行过程中,每次循环都会调用sleep或者wait等方法,那么不需要每次迭代都检查是否已中断。
*/
public class RightWayStopThreadWithSleepEvertLoop {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = ()->{
int num = 0;
try{
while(num <= 10000 /*&& !Thread.currentThread().isInterrupted()*/){
if(num%100==0){
System.out.println(num+"是100的倍数");
}
num++;
Thread.sleep(10);
}
}catch (InterruptedException e){
e.printStackTrace();
}
};
Thread thread= new Thread(runnable);
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
**
while内try/catch的问题
如果while里面放try/catch,会使中断失效
package threadcoreknowledge.stopthreads;
/**
* 描述: 如果while里面放try/catch,会导致中断失效
*/
public class CantInterrupt {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = ()->{
int num = 0;
while (num <= 10000&&!Thread.currentThread().isInterrupted()){
if(num %100 == 0){
System.out.println(num+"是100的倍数");
}
num++;
try {
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
}
};
Thread thread= new Thread(runnable);
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
运行结果
E:\.jdks\corretto-1.8.0_252\bin\java.exe "-javaagent:E:\IntelliJ IDEA 2019.3.5\lib\idea_rt.jar=53159:E:\IntelliJ IDEA 2019.3.5\bin" -Dfile.encoding=UTF-8 -classpath E:\.jdks\corretto-1.8.0_252\jre\lib\charsets.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\access-bridge-64.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\cldrdata.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\dnsns.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\jaccess.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\jfxrt.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\localedata.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\nashorn.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\sunec.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\sunjce_provider.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\sunmscapi.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\sunpkcs11.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\ext\zipfs.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\jce.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\jfxswt.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\jsse.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\management-agent.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\resources.jar;E:\.jdks\corretto-1.8.0_252\jre\lib\rt.jar;E:\workspace\thread-study\out\production\demo01 threadcoreknowledge.stopthreads.CantInterrupt
0是100的倍数
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at threadcoreknowledge.stopthreads.CantInterrupt.lambda$main$0(CantInterrupt.java:16)
at java.lang.Thread.run(Thread.java:748)
100是100的倍数
200是100的倍数
虽然报了异常,但是线程并没有真正停止。原因是Java中sleep一旦响应了中断,就会把interrupt的标记位清除,所以在sleep过程中收到了中断,catch住了也打印出了异常栈,但是interrupt标记位被清除了,所以在while的判断语句中也检查不到任何被中断过的迹象,所以打断不了线程。
实际开发中的两种最佳实践
1.优先选择传递中断:
在方法的签名中抛出异常,那么在run()中就会强制try/catch。throwInMethod()方法应该throws异常而不是直接try/catch处理异常,然后run()方法调用throwInMethod()方法就可以使用try/catch处理接收到中断信号并进行后续的处理。
package threadcoreknowledge.stopthreads;
/**
* 描述:
* 最佳实践: catch了InterruptedException之后的优先选择:在方法签名中抛出异常
* 那么在run()中就会强制try/catch
*/
public class RightWayStopThreadInProd implements Runnable{
@Override
public void run() {
while(true){
System.out.println("WDNMD");
try {
throwInMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void throwInMethod() throws InterruptedException {
// try {
// Thread.sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
Thread.sleep(2000);
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadInProd());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
2.不想传递或者无法传递中断的时候:恢复中断
在catch子语句中调用Thread.currentThread().interrupt()来恢复中断状态,以便于在后续的执行中,依然能够检查到刚才发生了中断。
package threadcoreknowledge.stopthreads;
/**
* 描述: 最佳实践2:在catch子语句中调用Thread.currentThread().interrupt()来恢复设置中断状态,以便于在后续的执行中,依然能够检查到刚才发生了中断
* 回到刚才RightWayStopThreadInProd补上中断,让它跳出
*/
public class RightWayStopThreadInProd2 implements Runnable{
@Override
public void run() {
while(true){
if( Thread.currentThread().isInterrupted()) {
System.out.println("Interrupted,程序运行结束");
break;
}
reInterrupt();
}
}
private void reInterrupt(){
// try {
// Thread.sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadInProd2());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
*不应该屏蔽中断!
响应中断的方法总结表:
- object.wait()/wait(long)/wait(long,int)
- Thread.sleep(long)/sleep(long,int)
- Thread.join()/join(long)/join(long,int)
- java.util.concurrent.BlokingQueue.take()/put(E)
- java.util.concurrent.locks.Lock.lockInterruptibly()
- java.util.concurrent.CountDownLatch.await()
- java.util.concurrent.CyclicBarrier.await()
- java.util.concurrent.Exchanger.exchanger(V)
- java.nio.channels.InterruptibleChannel相关方法
- java.nio.channels.Selector的相关方法
使用interrupt来停止线程的好处
被中断的线程拥有如何响应中断的权利,某些线程的代码非常重要的,需要等线程处理完之后由线程自己主动终止。
错误的停止线程的方法
1.被弃用的stop,suspend和resume方法
stop会导致线程突然停止,没法完成一个基本单位的操作,会造成脏数据。suspend和resume会让线程挂起,在恢复之前不会释放,会带着锁去休息,如果其他线程不及时唤醒它,或者其他线程需要这把锁来唤醒的话就会造成死锁。
2.用volatile设置boolean标记位
这个方法有局限性,在一定的情况下可行,在特定的情况下会失效。