一、线程中断的含义
中断线程是一种常见的多线程操作,试想一种情景:
用户因为下载任务慢,不耐烦地取消了下载任务。
此时就需要代码要为此实现中断功能。具体来说,就是主调线程给被调线程发一个中断信号,被调线程收到信号后结束执行run()方法,使得自身线程能立刻结束运行。
二、案例
对状态不同的线程调用 interrupt() 【即发送中断请求】,反馈也不同。
1. 对 运行(Runnable) 状态的线程调用 interrupt()
线程内部通过调用
isInterrupted()判断自己是否需要中断。
(1)先看被调线程的内部实现:
// MyThread1 线程内部实现
class MyThread1 extends Thread {
@Override
public void run() {
int n = 0;
// while 语句完成【响应中断】,具体代码为循环检测 isInterrupted()
while (!isInterrupted()){ // isInterrupted() 只能继承 Thread 类后才能调用父类方法。不能在实现 Runnable 接口(lambda 表达式)中被调用
n++;
System.out.println(n+" Hello");
}
}
}
可以看到,被调线程内部实际上是通过循环调用isInterrupted()检测自己是否收到中断信号,并以此决定是否终止。
(2)主调函数实现:
// main 线程
Thread t = new MyThread1();
t.start();
Thread.sleep(1); // 在 main 休眠的 1ms 内,t 在执行(循环打印 n + “Hello”)
t.interrupt(); // main 线程从休眠中恢复,调用 t.interrupt() 向 t 线程发送【中断请求】
t.join(); // 要求主线程等待,直到 t1 结束后再恢复执行 ** join() 是一个连接点,使 join() 语句前执行的一定是 t ,join() 后执行的一定是 main
System.out.println("end");
输出为:
1 Hello
2 Hello
3 Hello
...
26 Hello
end
a. t.start()启动进程后,主线程休眠 1ms 。在这一段时间内 t1完成了若干条打印。
b. 主线程恢复后,对 t 发送中断请求。这将使MyThread1内部继承自Thread的方法 isInterrupted()返回 true。
c. MyThread1内部定义的while()将因此终止,线程t也因此终止。
※ 注意,
isInterrupted()是继承自Thread的方法。如果你试图通过实现Runnable接口的方式来创建线程,那该方法是不存在的。
2. 对 等待(Waiting) 状态的线程调用 interrupt()
该线程的内部调用的
join()方法将立即抛出InterruptedException异常。因此,主调线程 t 只要在调用被调线程 base.join()时,捕获到 join() 所抛出的 InterruptedException 异常,说明有其它进程对 t 调用了 interrupt()
这次定义两种Thread类,调用关系为 Main -> MyThread -> BaseThread。
其中,BaseThread被故意设计为无限死循环执行,让MyThread在调用BaseThread时陷入等待状态。
当Main线程对MyThread执行interrupt()中断请求时,MyThread内部调用的base.join()将立刻抛出InterruptedException 异常。
Java 这么设计很好理解:
你自己( t )都要被中断了,自己所等待的其它线程( base )也没必要再等了。
(1)先看最底层的BaseThread实现:
// 故意让 BaseThread 无限循环执行,使 MyThread2 调用 BaseThread 会等待很久
class BaseThread extends Thread{
@Override
public void run() {
int n = 0;
while(!isInterrupted()){
n++;
System.out.println(n+" 次");
try {
Thread.sleep(100); //(1).在响应中断之前,每打印一条,睡眠 100 ms
} catch (InterruptedException e) {
break;
}
}
}
}
就是定义了一个将会无限死循环的线程。
(2)MyThread线程实现
class MyThread extends Thread{
@Override
public void run() {
Thread base = new BaseThread();
base.start();
try {
base.join(); // (2).base 将无限循环执行下去,join() 会让 MyThread 线程从这一句开始一直保持 Waiting
} catch (InterruptedException e) {
System.out.println("interrupted");//(5).t 内部的 base.join() 抛出异常,转到 catch 语句。这么设计很好理解,你自己(t)都要被中断了,自己所等待的其它线程(base)也没必要再等了。
}
base.interrupt(); // (6).如果没有这一句,base 线程将在 t 线程终止后继续无限循环下去。
}
}
由于BaseThread将会无限死循环,调用base.join()时会让MyThread陷入 Waiting 状态。
(3)Main线程实现
Thread t = new MyThread();
t.start();
Thread.sleep(1000);// (3).主线程休眠 1000 ms , 足以执行完「 t 调用 base -> base 进入无限循环 -> t 因调用 base.join() 陷入无限等待」的过程
t.interrupt();// (4).此时 t 已经因 base.join() 陷入等待,对 t 进行中断,t 内部的 base.join() 语句将立刻抛出 InterruptedException
t.join();
System.out.println("end");
Main在启动后休眠 1000ms,这么长时间,足够执行完
t 调用 base ->
base 进入无限循环 ->
t 因调用 base.join() 陷入无限等待
的过程。
此时main再对 t(处于等待状态)进行interrupt()操作,将会引发t内部调用的base.join()抛出InterruptedException。
然后 t内部开始执行 catch语句,并准备终止自己。
※ 注意:
- 代码注释内的序号代表了各线程执行的实际顺序。
- 在注释(6)中,如果
t在自己终止前没有要求中断base,base将一直存活。换言之,因其而生 却不 因其而死。