文章目录

一个在行为良好的软件与勉强运行的软件之间的最主要区别就是:
行为良好的软件能很完善地处理失败、关闭和取消



一、简介


​Java​​没有提供任何机制来安全地终结线程。

​Thread.stop** 和​​suspend`等方法提供了终结,但由于存在一些严重的缺陷,因此应该避免使用

中断​​Interruption​​:这是一中协作机制,能够使一个线程终止另一个线程的当前工作。



(1)问题



1. 为什么需要线程中断?

用于处理失败、关闭和取消等过程。

  1. 在​​Java​​中没有一种安全的抢占式方法来停止线程,因此也就没有安全的抢占方式来停止任务。

即,采用协作式的机制(interruption),使请求取消的任务和代码都遵循一种协商好的协议。
通过推迟中断请求的处理,开发人员能指定更灵活的中断策略,从而使应用程序在响应性和健壮性之间实现合理的平衡。



2. 什么是线程中断?

中断:它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己。

线程在调用​​Object class​​​的​​wait()​​​ ​​join()​​​ ​​sleep()​​​方法时被阻塞,那么​​interrupt​​​会生效,该线程的中断状态将被清除,抛出​​InterruptedException​​异常

如果目标线程是被 I/O 或者 NIO中的​​Channel​​所阻塞,同样,I/O操作会被中断或者返回特殊异常值。达到终止线程的目的。

如果以上条件都不满足,则会设置此线程的中断状态

每个线程都有一个​​boolean​​​类型的中断状态。
当中断线程时,这个线程的中断状态将被设置为 true。



3. 有什么中断策略?

当发现中断请求时,应该做哪些工作(如果需要的话),哪些工作单元对于中断来说是原子操作,以及以多快的速度来响应中断。

最合理的中断策略:某种形式的线程级(Thread-Level)取消操作或服务级(Service-Level)

取消操作:尽快退出,在必要时进行清理,通知某个所有者该线程已退出。

区分 任务线程

  • 任务:不会在其自己的拥有的线程中执行,而是在某个服务(例如线程池)拥有的线程中执行。

对于非线程所有者的代码来说(例如,对于线程池而言,任何在线程池实现以外的代码),应该小心地保存中断状态,这样拥有线程的代码才能对中断作出响应,即使 “非所有者” 代码也可以作出响应。

比如:
当你为一户人家打扫房屋时,即使主人不在,也不应该把在这段时间内收到的邮件扔掉,而应该把邮件收起来,等主人回来以后再交给他们处理



4. 如何响应中断?

当调用可中断的阻塞函数时,例如​​Thread.sleep​​​或​​BlockingQueue.put​​等

有两种实用策略可用于处理 ​​InterruptedException​​:

  1. 传递异常(可能在执行某个特定于任务的清除操作之后),从而使你的方法也可以成为可中断的阻塞方法。
  2. 恢复中断状态,从而使调用栈中的上层代码能够对其进行处理。
try {
Thread.sleep(10000);

} catch (InterruptedException e) {

// 再次调用
Thread.interrupt();
}




二、取消与中断


通常,中断是实现取消的最合理方式。

(1)取消 ​​Thread.stop​

public class InterruptedThread {

static class MyThread extends Thread {

private int i = 0;
private int j = 0;

public void run() {

synchronized (this) {

++i;
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
++j;
}
}

public void print() {

System.out.println("i = " + i + " j = " + j);
}
}

public static void main(String[] args) throws InterruptedException {

MyThread myThread = new MyThread();

myThread.start();

Thread.sleep(1000);

myThread.stop(); // 不建议这样做

while (myThread.isAlive()) {

}

myThread.print();
}
}

结果如下:
i = 1 j = 0

说明:当调用​​Thread.stop​​​时候,线程就直接退出了,没有执行 ​​++j​



(2)中断 ​​Thread.interrupte​

public class InterruptedThread {

static class MyThread extends Thread {

private int i = 0;
private int j = 0;

public void run() {

synchronized (this) {

++i;
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
++j;
}
}

public void print() {

System.out.println("i = " + i + " j = " + j);
}
}

public static void main(String[] args) throws InterruptedException {

MyThread myThread = new MyThread();

myThread.start();

Thread.sleep(1000);

myThread.interrupt();

while (myThread.isAlive()) {

}

myThread.print();
}
}

结果:

java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.donaldy.thread.interrupt.InterruptedThread$MyThread.run(InterruptedThread.java:16)
i = 1 j = 1




三、中断几种方式


(1)取消标志 ​​volatile​

private volatile boolean cancelled;

while(!cancelled) {
// do something
}



(2)​​Thread.interrupt()​

Thread.interrupt();



(3)使用 ​​Future​​ 来取消

Future<?> task = taskExec.submit(r);

task.cancel(true);




四、阻塞队列处理


背景:
生产者线程生成素数,并将它们放入一个阻塞队列。

如果生产者的速度超过了消费者的处理速度,队列将被填满,​​put​​方法也会阻塞。

这时候:
当生产者在​​​put​​方法中阻塞时,如果消费者希望取消生产者任务,那么将发生什么情况?



(1)使用标识状态 ​​volatile​

它可以调用 ​​cancel​​​ 方法来设置 ​​cancelled​​​标识,
但此时生产者却永远不能检查这个标志,
因为它无法从阻塞的 ​​​put​​方法中恢复过来(因为队列一直是满的,没有唤醒它)

这就导致结果:
任务可能永远不会检查取消标志,因此永远不会结束。

public class BrokenPrimeProducer extends Thread {

private final BlockingQueue<BigInteger> queue;

private volatile boolean canclled = false;

BrokenPrimeProducer(BlockingQueue<BigInteger> queue) {
this.queue = queue;
}

public void run() {
try {
BigInteger p = BigInteger.ONE;
while (!canclled) {
queue.put(p = p.nextProbablePrime());
}
} catch (InterruptedException consumed) {}
}

public void cancel() {
this.canclled = true;
}
}



(2)使用中断

每次迭代循环中,有两个位置可以检测出中断:

  1. 在阻塞的​​put​​​方法调用中
    ​​​queue.put()​
  2. 循环开始处查询中断状态
    ​​​(!Thread.currentThread().isInterrupted())​
public class PrimeProducer extends Thread {

private final BlockingQueue<BigInteger> queue;

PrimeProducer(BlockingQueue<BigInteger> queue) {
this.queue = queue;
}

public void run() {
try {
BigInteger p = BigInteger.ONE;
while (!Thread.currentThread().isInterrupted()) {
queue.put(p = p.nextProbablePrime());
}
} catch (InterruptedException consumed) {
// 允许线程退出
}
}
}