任务和线程的启动很容易。在大多数时候,我们都会让它们运行直到结束,或者让它们自行停止。然而,有时候我们希望提前结束任务或线程,或许是因为用户取消了操作,或者应用程序需要被快速闭要使任务和线程能安仝、快速、可靠地停止下来,并不是一件容易的事。Java的Thread类为我们提供了stop()
,suspend()
等停止挂起线程的方法,但是由于安全问题目前都已被弃用。Java并没有提供一种安全的抢占式
方法来停止线程,但它提供了中断(Interruption),这是一种协作机制
,采用协作式
的方式使一个线程终止另一个线程的当前工作。 这种协作式的方法是必要的,我们很少希望某个任务、线程或服务立即停止,因为这种立即停止会使共享的数据结构处于不一致的状态。相反,在编写任务和服务时可以使用一种协作的方式:当需要停止时,它们首先会清除当前正在执行的工作,然后再结束。这提供了更好的灵活性,因为任务本身的代码比发出取消请求的代码更清楚如何执行清除工作。 生命周期结束(End-of-Lifecycle)的问题会使任务、服务以及程序的设计和实现等过程变得复杂,而这个在程序设计中非常重要的要素却经常被忽略。一个在行为良好的软件与勉强运行的软件之间的最主要区别就是,行为良好的软件能很完善地处理失败、关闭和取消等过程。如何设计一种协作机制,让线程可以安全的中断呢?我们可以设置一个取消标志,在工作线程会被中断的地方去检查这个标志,当检查到这个中断标志被设置为已取消时,工作线程便开始做取消工作。
public class CancelableThread implements Runnable {
// 线程取消标志,volatile修饰,保证内存可见性
private volatile boolean isCanceled = false;
@Override
public void run() {
while (!isCanceled) {//在工作线程中轮询检测这个取消标志
System.out.println("The current thread is doing something...");
System.out.println(Thread.currentThread().getName() + " cancel flag is " + isCanceled);
}
// 当取消标志被设置为true,执行以下代码,可以做一些取消工作
System.out.println(Thread.currentThread().getName() + "The current thread Has been cancelled");
}
private void cancel() {
isCanceled = true;
}
}
public class MainTest {
public static void main(String[] args) throws Exception {
CancelableThread cancelableThread = new CancelableThread();
new Thread(cancelableThread).start();
try {
Thread.sleep(100);
} finally {
// 设置标志位为true,来中断线程
cancelableThread.cancel();
}
}
}
- 打印结果
Thread-0 cancel flag is false
The current thread is doing something...
Thread-0 cancel flag is false
The current thread is doing something...
Thread-0 cancel flag is false
The current thread is doing something...
Thread-0 cancel flag is false
The current thread is doing something...
Thread-0 cancel flag is true
Thread-0The current thread Has been cancelled
总结一下上面的例子,这个例子有个缺陷,run方法如果这样写:
@Override
public void run() {
while(!isCanceled){
try {
// 这里相当于线程被挂起了
Thread.sleep(10000);
}catch (InterruptedException e){
// 这里用的是isInterrupted方法,标志位被清除了
}
}
// 检测到中断标志,跳出循环
}
假如当前线程执行到了sleep
方法,线程被挂起了,无论标志位isCanceled
怎么变,根本跳不出循环,这样很可能导致和我们预期的结果不一致,所以不建议使用自定义的标志位来控制线程的中断,应当用下面的方法。
Thread类为我们提供了三个与线程中断相关的方法,来实现上述机制。这三个方法分别是:
public void interrupt() {
//... 省略相关代码
interrupt0(); // Just to set the interrupt flag
//... 省略相关代码
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
-
interrupt()
方法主要用来设置中断标志位;如果此线程在调用wait
方法或join
、sleep
方法阻塞时,那么它的中断状态将被清除(也就是线程不会理会中断标志位),并且会收到一个InterruptedException
异常。 - 静态的
interrupted()
方法用来测试当前线程是否被中断,调用此方法会清除线程的中断状态。如果当前线程被中断,则为true;否则为false。 -
isInterrupted()
方法用来测试当前线程是否被中断,但是不会清除线程的中断状态。
public class InterruptTest {
static class InnerThread extends Thread{
@Override
public void run() {
while(!isInterrupted()){
System.out.println(Thread.currentThread().getName()+" cancle flag is "+isInterrupted());
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
// 抛出InterruptedException,中断标志位被清除,再次调用 interrupt();
interrupt();
}
}
System.out.println(Thread.currentThread().getName()+" cancle flag is "+isInterrupted());
}
}
public static void main(String[] args) {
InnerThread innerThread = new InnerThread();
innerThread.start();
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
innerThread.interrupt();
// InnerThread innerThread2 = new InnerThread();
// innerThread2.start();
// innerThread2.interrupt();
}
}
- 打印结果
Thread-0 cancle flag is false
Thread-0 cancle flag is false
Thread-0 cancle flag is false
Thread-0 cancle flag is false
Thread-0 cancle flag is false
Thread-0 cancle flag is false
Thread-0 cancle flag is false
Thread-0 cancle flag is false
Thread-0 cancle flag is false
Thread-0 cancle flag is false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at InterruptTest$InnerThread.run(InterruptTest.java:13)
Thread-0 cancle flag is true
文章出处(内容有所修改):https://www.cnblogs.com/perkins/p/9052139.html
总结一下:
- 一般情况下不建议使用自定义的中断标志位,原因上面说明了
-
interrupt
和isInterrupted
方法配合使用,能解决绝大多数的中断业务 - 在调用
wait
、sleep
、join
等阻塞或者挂起方法的时候,会抛出中断异常,为什么这么设计?这是为了在线程由于某种原因被挂起后,后续的业务如果说还没执行完,强制中断线程会导致不可预料的后果(比如说文件写入了一半),如果就是想中断线程,可以在catch块中再次调用interrupt
方法 - 静态的
interrupted()
和isInterrupted()
方法都是调用的private native boolean isInterrupted(boolean ClearInterrupted)
根据传入的ClearInterrupted
的值,来判断是否要清除中断标志位 - wait、notify、notifyAll为什么被定义在了Object类中?这些都跟锁有关,锁是属于对象的