如何优雅地退出一个线程

宋正兵 on 2021-06-08

对于一个需要被终止的线程,大体上有三种方法来退出它。

大家肯定有听说过 stop() 方法,通过 stop() 方法可以很方便地终止一个线程,但是它在结束一个线程地时候不会保证线程地资源正常释放。假如有一个线程正在处理一个复杂地业务流程,这个线程突然间被调用 stop() 方法而意外终止,此时地业务数据可能会产生不一致地问题。正是因为这个原因,stop() 方法被标记为 @Deprecated 被废弃地方法,JDK 在以后的版本中可能会移除它,不推荐使用。

标志位

利用 volatile 关键字实现多线程之间共享变量的可见性这一特点来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private volatile static boolean stop = false;
public static void main(String[] args) throws Exception {
Thread thread = new Thread(() -> {
int i = 0;
while (!stop) {
// 操作
i++;
}
System.out.println(i);
});
thread.start();
TimeUnit.MILLISECONDS.sleep(50);
stop = true;
}

interrupt

当其他线程通过调用当前线程的 interrupt 方法,表示向当前线程打个招呼,告诉他可以中断线程的执行了。这里又分为两种情况:

  • 线程处于阻塞状态:当前线程被 sleep、Thread.wait、Thread.join 三种方法之一阻塞时,调用当前线程的 interrupt 方法会抛出 InterruptedException 异常。通过捕获该异常,然后进行结束线程操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public static void main(String[] args) throws Exception {
    Thread thread = new Thread(() -> {
    while (true) {
    try {
    // 阻塞
    Thread.sleep(10000);
    } catch (InterruptedException e) {
    // 抛出该异常,会将复位标识设置为false
    System.out.println(Thread.currentThread().isInterrupted());
    e.printStackTrace();
    // 退出循环
    break;
    }
    }

    });
    thread.start();
    // 设置中断标识,中断标识为true
    thread.interrupt();
    TimeUnit.SECONDS.sleep(1);
    System.out.println(thread.isInterrupted()); // false
    }
  • 线程未处于阻塞状态:使用 isInterrupt() 方法判断线程的中断标志来退出循环。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public static void main(String[] args) throws Exception {
    Thread thread = new Thread(() -> {
    while (true) {
    boolean interrupted = Thread.currentThread().isInterrupted();
    if (interrupted) {
    System.out.println("before: "+ interrupted); // true
    // 【清除打断标记】对线程进行复位,中断标识为false
    Thread.interrupted();
    System.out.println("after: "+ Thread.currentThread().isInterrupted()); // false
    // 跳出循环
    break;
    }
    }

    });
    thread.start();
    // 设置中断标识,中断标识为true
    thread.interrupt();
    }

两阶段终止模式

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class TwoPhaseTermination {
private Thread monitor;
public void start() {
monitor = new Thread(() -> {
Thread thread = Thread.currentThread();
if (thread.isInterrupted()) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(2000);
log.debug("执行监控记录");
} catch (InterruptedException e) {
e.printStackTrace();
// 有异常,重新设置打断标记为true,否则的话无法进入下一次while
thread.interrupt();
}
});
monitor.start();
}
public void stop() {
monitor.interrupt();
}
}
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
twoPhaseTermination.start();
Thread.sleep(6500);
twoPhaseTermination.stop();
}
}
/**
2020-06-01 21:25:53.065 DEBUG TwoPhaseTermination - 执行监控记录
2020-06-01 21:25:55.068 DEBUG TwoPhaseTermination - 执行监控记录
2020-06-01 21:25:57.069 DEBUG TwoPhaseTermination - 执行监控记录
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at TwoPhaseTermination.lambda$start$0(Test.java:34)
at java.lang.Thread.run(Thread.java:745)
2020-06-01 21:25:57.563 DEBUG TwoPhaseTermination - 料理后事

Process finished with exit code 0
**/