文章目录
1.中断线程的几种方式
- 线程正常或使用标识字段终止线程内的循环,run()方法执行结束后自然终止。
- 使用stop()方法强行中断。
- 使用interrupt让线程自行决定是否要中断。
1.1.stop()方法
stop()方法可以强行中断一个正在执行的线程,但是JDK的API中,这是一个已经过时的方法,不建议再使用。
为什么要弃用stop()方法呢?
因为它是不安全的,它的不安全性在于:调用stop()时会释放所有锁, 并且线程停止的时机不可控,我们无法得知这个线程停止时执行到了哪个位置。
举个例子:线程A将一个user对象从name=张三,age=18,修改为name=张三丰,age=108,将这个过程加入到同步代码块中保证线程安全。线程A执行完后,线程B去读取这个user对象,得到的结果就是name=张三丰,age=108。
如果在线程A运行的过程中,main线程调用了线程的stop()方法,在线程A将name修改为张三丰后中断了线程,并释放锁。此时线程B抢到锁后,就得到的结果就是name=张三丰,age=18,这样的结果就是错误的。
1.2.标识字段
在不使用stop()方法的情况下,可以自定义一个标识字段,通过判断标识字段值来决定要不要退出循环。
这种方式一般使用在线程里面用了while(true)这样的的情况下,如果是不耗时的一次异步调用,直接等线程自然结束就可以了。
下面用一个Demo来跑一下这种方式:
public class FlagDemo implements Runnable {
/**
* volatile修饰保证可见性
*/
private static volatile boolean stopFlag = false;
@Override
public void run() {
System.out.println("开始循环");
while (!stopFlag) {
// 什么也不做
}
System.out.println("停止循环");
}
public static void main(String[] args) {
new Thread(new FlagDemo()).start();
stopFlag = true;
}
}
此时控制台打印出,开始循环,停止循环后线程终止,如果不修改stopFlag值,while循环就一直不会停下来。
这种方式可以让线程退出无线循环,但是如果线程是阻塞状态的话,也是中断不了的。例如在while(!stopFlag)中sleep2000秒。
@Override
public void run() {
System.out.println("开始循环");
while (!stopFlag) {
try {
TimeUnit.SECONDS.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("停止循环");
}
此时只会打印出一个开始循环,然后线程就阻塞了,无法进入下一个循环。这种情况可以使用第三种方式来中断,也就是interrupt()。
2.优雅的中断线程——interrupt
2.1.interrupt相关的方法
在Thread类中有三个和中断相关的方法,interrupt() ,interrupted(),isInterrupted()。
- interrupt:用于改变当前线程的中断标识,如果线程处于等待阻塞状态,则会抛出InterruptedException。需要注意的是,这个方法只修改标识,而不是直接中断一个线程。
- isInterrupted:检查当前线程状态是否是中断状态,如果是则返回true,不是返回false,调用后不会重置中断状态。
- interrupted:也是检查当前线程状态是否是中断状态,如果是则返回true,不是返回false,调用后会重置中断状态,这是个静态方法,可以用Thread类调用。
2.2.两个interrupt中断线程的例子
2.2.1.用做标识退出while循环
将1.2中的使用标识字段的例子改写成interrupt。
public class InterruptDemo1 implements Runnable {
@Override
public void run() {
System.out.println("开始循环");
while (!Thread.currentThread().isInterrupted()) {
// 什么也不做
}
System.out.println("停止循环");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new InterruptDemo1());
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}
}
执行后成功打印出停止循环,退出了while无限循环。
2.2.2.用于打断等待阻塞
在1.2的第二个例子中使用了sleep导致线程阻塞,这个时候标识字段就无效了,除此之外还有wait,join也会导致同样的线程阻塞问题。
这个时候可以通过interrupt中断阻塞,并抛出InterruptedException异常,在catch中由当前线程自行选择是否需要结束。
public class InterruptDemo2 implements Runnable {
@Override
public void run() {
System.out.println("开始循环");
while (!Thread.currentThread().isInterrupted()) {
try {
TimeUnit.SECONDS.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
/*
* 1.在此处由线程自行选择是否中断
* 2.抛出InterruptedException后中断状态会重置为false,如果要选择中断循环,需要再次调用interrupt
*/
Thread.currentThread().interrupt();
}
}
System.out.println("停止循环");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new InterruptDemo2());
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}
}
打印的结果为:
开始循环
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.ls.practice.concurrent.interrupt.InterruptDemo2.run(InterruptDemo2.java:15)
at java.lang.Thread.run(Thread.java:745)
停止循环
2.3.isInterrupted和interrupted使用的区别
先看一下两者的源码:
public boolean isInterrupted() {
return isInterrupted(false);
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
// 上面两个方法,最终都是调用了本地方法
private native boolean isInterrupted(boolean ClearInterrupted);
传入的布尔值为true的意思是返回当前的中断状态后,将中断状态复位为false,而传入false表示不复位。
2.3.1.使用interrupted静态方法可能遇到的坑
先看一个示例,想一下可能的打印结果。
public class InterruptedDemo1 implements Runnable {
@Override
public void run() {
while (true) {
}
}
public static void main(String[] args) {
Thread thread = new Thread(new InterruptedDemo1());
thread.start();
// 设置中断状态为true
thread.interrupt();
// 获取中断状态
System.out.println(thread.isInterrupted());
// 获取中断状态并重置
System.out.println(thread.interrupted());
// 获取中断状态并重置
System.out.println(Thread.interrupted());
// 获取中断状态
System.out.println(thread.isInterrupted());
}
}
输出结果如下:
true
false
false
true
第二次打印的时候就重置了,为什么不是true,false,false,false呢?这就是需要注意的点了,interrupted方法中使用的是currentThread(),也就是说,复位的状态是主线程的状态和此处new的线程没有关系。
2.3.2.interrupted()线程的复位的验证
下面分别使用isInterrupted()和interrupted()做while循环的中断标识,在中断后打印当前线程的中断状态,对比两者的区别。
先测试isInterrupted()
public class InterruptedDemo2 implements Runnable {
@Override
public void run() {
System.out.println("开始循环");
while (!Thread.currentThread().isInterrupted()) {
}
System.out.println(Thread.currentThread().isInterrupted());
System.out.println("停止循环");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new InterruptedDemo2());
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}
}
此时打印出的结果为:true
在测试interrupted
public class InterruptedDemo2 implements Runnable {
@Override
public void run() {
System.out.println("开始循环");
while (!Thread.interrupted()) {
}
System.out.println(Thread.currentThread().isInterrupted());
System.out.println("停止循环");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new InterruptedDemo2());
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}
}
打印结果为false,表示线程状态已重置。
3.总结
①stop()和interrupt()最根本的区别在于,stop()是“他杀”,不管将要被中断线程的执行状态是什么,直接强制中断。而interrupt()是改变将要被中断的线程标识,被中断的线程中可以根据自身中断标识的变化自行决定要不要中断,并且可以根据其他的条件判断什么时候中断。
②自定义标识字段来结束线程无法解决等待阻塞的问题,这时候可以通过interrupt()让线程抛出InterruptedException来激活线程,打断阻塞的状态。