Get into the habit of writing together! This is the 4th day of my participation in the "Nuggets Daily New Plan·April Update Challenge", click to view the details of the event .
Introduction
We can start the thread by calling the start()
method , but stopping the thread is a bit more complicated.
Typically, instead of manually stopping a thread, we allow the thread to run until the entire process ends, and then let it stop naturally. However, for some special cases, the thread needs to be stopped in advance, such as the user suddenly closes the program, the program runs in error, etc. Threads that are about to be stopped at this time are still valuable in many business scenarios, but Java
they do not provide the ability to directly and safely stop threads that are easy to use.
Java
For , the most correct way to stop a thread is to use interrupt()
, but it only serves as a notification to the thread being stopped. For the stopped thread, it has full autonomy, and can choose to stop immediately, after a period of time, or not at all.
Why Java
not provide the ability to force stop a thread?
Java
It is hoped that programs can notify each other and manage threads cooperatively- Because if you don't know what the other party is doing, rushing to stop the thread may cause some security problems
- For example, when a thread is writing a file, when it receives a termination signal, it needs to decide according to its own business whether to choose to stop immediately, or to stop after writing the entire file successfully, and if it chooses to stop immediately, it may cause data confusion. Neither the originator of the interrupt command nor the receiver want data problems
The wrong way to stop a thread
The following three ways of stopping a thread are marked as obsolete @Deprecated
:
stop()
: Stop the thread from running.stop()
The thread will be stopped directly, although it will release the lock, but it will cause the task to stop abruptly, not giving the thread enough time to process the finishing work, and data integrity and other problems may occur.suspend()
/resume()
: Suspend thread execution / Resume thread execution.suspend()
After the thread calls , the lock is not released.resume()
The lock will not be released until the thread is called. This can lead to deadlock problems.- With the help of
volatile
marker bits
Why is it wrong to stop a thread with the help of the volatile
flag bit?
In some cases, the volatile
flag bit can stop the thread gracefully, as follows:
public class VolatileStopThread implements Runnable {
private volatile boolean canceled = false;
@Override
public void run() {
int num = 0;
try {
while (!canceled && num <= 1000000) {
if (num % 10 == 0) {
System.out.println(num + "是10的倍数");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
VolatileStopThread r = new VolatileStopThread();
Thread thread = new Thread(r);
thread.start();
Thread.sleep(3000);
r.canceled = true;
}
}
复制代码
However, if the stopped thread is blocked in the middle of execution (such as adding an element to a full blocking queue), then it will not be able to detect that the volatile
flag bit has been set false
, and will not be able to break out of the loop. So the thread cannot be stopped with the help of the volatile
flag bit.
interrupt() method and interrupt flag bit
The interrupt()
method can stop the thread, which is divided into two cases here:
- Calling a
interrupt()
method sets the interrupt flag bit. - Calling the
interrupt()
method , such as the thread that calls thesleep()
,wait()
,join()
method, will throwInterruptedException
an exception , and at the same time clear the interrupt flag, that is, set it tofalse
.
What are the ways to access interrupt flag bits?
isInterrupted()
: This method does not affect break flags.- Static method
Thread.interrupted()
: After returning the break flag, it will be set tofalse
. That is, this method can be used to clear the interrupt flag bit.
Stop the thread correctly
Method 1 : Declare throwing an exception in the method signature run()
, and use in the method to try/catch
catch the exception.
- 用
throws InterruptedException
标记方法,不捕获异常,以便该异常可以传递到顶层的run()
方法 - 由于
run()
方法无法抛出受检异常,所以只能使用try/catch
捕获异常,这样的好处在于,顶层方法必须处理该异常
方式二:在 catch
块中再次中断线程。
- 在
catch
块中调用Thread.currentThread().interrupt()
函数 - 因为如果线程在休眠期间被中断,那么会自动清除中断信号
- 这时如果手动添加中断信号,中断信号仍然可以被捕捉到
如果盲目地忽略中断请求,也就是屏蔽中断信号,可能会导致线程无法被正确停止。
两阶段终止模式
上面的方式二其实就是两阶段终止模式。
两阶段终止模式 Two-Phase Termination Pattern
是一种多线程设计模式,用于在线程 t1
中优雅地停止另一个线程 t2
,“优雅”指的是给被停止的线程做收尾工作的机会。
两阶段终止分为两种中断情况:
- 运行时中断,打断标记已经为
true
,只需要检测即可 - 睡眠时中断,需要捕捉异常,以及重新设置打断标记为
true
团子注:使用
interrupt()
实现两阶段终止。关键点在于对于sleep
、join
或者wait
等处于阻塞状态的线程,interrupt()
只会抛出InterruptedException
,而不会设置打断标记,所以只要在异常处理内容中手动再次interrupt()
重新设置打断标记即可。
流程图如下:
示例代码如下:
class TPTInterrupt {
private Thread thread;
public void start(){
thread = new Thread(() -> {
while(true) {
Thread current = Thread.currentThread();
if(current.isInterrupted()) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("将结果保存");
} catch (InterruptedException e) {
current.interrupt();
}
// 执行监控操作
}
}, "监控线程");
thread.start();
}
public void stop() {
thread.interrupt();
}
}
复制代码
调用:
TPTInterrupt t = new TPTInterrupt();
t.start();
Thread.sleep(3500);
log.debug("stop");
t.stop()
复制代码
结果:
11:49:42.915 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:43.919 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:44.919 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:45.413 c.TestTwoPhaseTermination [main] - stop
11:49:45.413 c.TwoPhaseTermination [监控线程] - 料理后事
复制代码
参考资料
- Pull Hook:
Java
Concurrent Programming78
Lecture Part2
Lecture - [[202112052113 Java multithreading: interrupt method]]
- [[202112052155 Java Multithreading Design Patterns: Two-Phase Termination]]
- [[202112052217 Java Multithreading: Deprecated Obsolete Method]]