并发编程 终止线程

本文将介绍如何终止线程以及如何终止线程池。

一、如何终止线程?

不建议使用 stop() 方法,它会直接杀死线程,可能会因启占有的锁未释放而影响后续其它线程操作。

建议使用 interrupt(),它所作的事情是通知线程终止。线程可以在收到终止信号后进行一些后续操作并自行结束。

二、interrupt()

interrupt() 的作用是:通知线程应该结束。

需要注意的是,具体要结束还是要继续运行,可以由被通知的线程自行决定。

具体而言,对一个线程调用 interrupt() 时,

  • 如果线程处于阻塞状态(调用了 wait()join()sleep() 方法),则线程的中断标志将被设为 true,并且退出阻塞状态并抛出一个 InterruptedException,JVM 会将触发异常时线程的中断标志重置,将中断标志重新设为 false

  • 如果线程处于正常活动状态,则线程的中断标志将被设为 true

  • 如果线程正在获取锁,且该锁无法被 interrupt()(synchronized、JUC 包下无法被中断的锁),则线程不会被 interrupt() 影响

三、两阶段终止模式

1. 什么是两阶段终止模式?

所谓两阶段终止模式,就是将终止过程分为两个两个阶段:

  • 阶段一:线程 A 向线程 B 发送终止指令
  • 阶段二:线程 B 响应终止指令

2. 简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Test {

Thread thread;

synchronized void start(){
thread = new Thread(()->{
// 判断线程中断状态,当线程 "已被中断" 时,停止工作
while (!Thread.currentThread().isInterrupted()){
// ··· do something ···
}
});
thread.start();
}

synchronized void stop(){
thread.interrupt();
}
}

2. 简单示例 - 考虑阻塞

如果线程处于阻塞状态(调用了 wait()join()sleep() 方法),则线程的中断标志将被设为 true,并且退出阻塞状态并抛出一个 InterruptedException,JVM 会将触发异常时线程的中断标志重置,将中断标志重新设为 false

因此,应该捕捉 InterruptedException,并重新设置线程的中断状态,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Test {

Thread thread;

synchronized void start(){
thread = new Thread(()->{
// 判断线程中断状态,当线程 "已被中断" 时,停止工作
while (!Thread.currentThread().isInterrupted()){
try {
// ··· do something ···
} catch (InterruptedException e) {
// 重新设置线程中断状态
Thread.currentThread().interrupt();
}
}
});
thread.start();
}

synchronized void stop(){
thread.interrupt();
}
}

3. 简单示例 - 自定义的线程终止标志位

此版代码主要解决这样一个问题:我们常常需要调用第三方方法,无法保证第三方方法中是否会对 InterruptedException 做正确处理,因此可以增加自定义的线程终止标志位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Test {

/**
* 线程终止标志位
*/
volatile boolean terminated = false;

Thread thread;

synchronized void start(){
terminated = false;
thread = new Thread(()->{
// 判断线程中断状态,当线程 "已被中断" 时,停止工作
while (!terminated){
try {
// ··· do something ···
} catch (InterruptedException e) {
}
}
});
thread.start();
}

synchronized void stop(){
terminated = true;
thread.interrupt();
}
}

四、终止线程池

线程池提供了两个方法:

  • shutdown():拒绝接收新任务;待正在执行的任务和等待队列的任务执行完成后,关闭线程池

  • shutdownNow():拒绝接收新任务;中断正在执行的任务;将等待队列的任务作为方法返回值返回,丢弃等待队列中的任务;关闭线程池

    如果需要调用此方法,则在提交的任务中,需要应用两阶段终止模式处理线程的终止

参考

  • Java 并发编程实战