线程的中断与停止
对于线程的停止,通常情况下我们是不会去手动去停止的,而是等待线程自然运行至结束,但在实际开发中,很多情况中需要我们提前去手动来停止线程,比如程序中出现异常错误、使用者关闭程序等情况中。如果不能很好地停止线程那么可能会导致各种问题,所以正确的停止线程是非常的重要的。
常见的中断线程的方式有以下几种:
1、方式一(不推荐使用)
使用 Thread 类的 stop() 方法来终止线程:
Thread 类的 stop() 方法虽然可以终止线程,但该方法已被标识为废弃方法,原因是 stop() 方法太过暴力,即使线程只执行一半,也会被强行终止,不能保证线程资源正确释放,线程不安全,从而产生不可预料的结果,因此不提倡使用,可能会造成意想不到的结果。
2、方式二(不推荐使用)
根据 volatile
修饰的标志位判断线程是否需要中断:
public static class ChangeObjectThread extends Thread {
// 表示是否停止线程
private volatile boolean stopMe = true;
public void stopMe() {
stopMe = false;
}
@Override
public void run() {
while (!stopMe) {
System.out.println("I'm running");
}
}
}
在上面的代码里面,定义了一个标记变量 stopMe,用于标识线程是否需要退出,当 stopMe() 方法被调用时,stopMe 就会被赋值为 false,此时在代码里面的 while(!stopMe) 就会检测到这个改动,线程就退出了,实际上会存在延迟。
3、方式三(推荐使用)
通过 interrupt 方法中断机制终止线程:
该方式的核心就是通过 interrupt() 方法设置线程的中断标志位,并通过 isInterrupt()与interrupted() 方法监视并判断中断信号,当线程检测到为 true 时则说明接收到中断信号,此时需要被中断线程做相应的处理。但如何去响应这个中断信号,被中断线程有完全的自主权,也就是中断结果是死亡或是继续运行,取决于这个被中断线程本身的逻辑,较为合理且可控性较高。
Thread.interrupte():设置线程的中断标志位为 true,表示被其他线程进行了中断操作,但它不会像 stop() 方法那样强制中断正在运行的线程,仅仅起到通知被停止线程的作用;而被中断线程,则需要通过监视自身的标志位是否被中断来进行响应,比如使用 isInterrupted() 或 interrupted() 方法来判断是否被中断;
this.interrupted():测试当前线程是否已经中断。如果连续两次调用该方法,第一次返回 true,第二次返回false,因为 interrupted() 方法具有清除状态的功能,它内部实现是调用的当前线程的 isInterrupted(),并且会重置当前线程的中断状态。
this.isInterrupted():测试线程是否已经中断,但是不会清除状态标识
前面说过,interrupt 机制需要被中断线程去监视中断标志位是否发生变化并做处理,那么当线程处于阻塞状态,确切来说,线程被 Object.wait()、Thread.join() 和 Thread.sleep() 三种方法之一阻塞时,应该如何处理呢?其实 interrupte() 方法也是支持线程阻塞情况的,一旦在上面几种情况下,线程的中断状态被置为“中断”,就会抛出中断异常 InterruptedException,从而提早地终结被阻塞状态,所以我们只需要捕获 InterruptedException 异常并对中断进行响应即可。不过在抛出 InterruptedException 之前,JVM 会先将该线程的中断标志位复位,所以此时调用 isInterrupted() 方法将会返回 false,但如果线程既没有被阻塞,又没有通过 isInterrupted()/isInterrupted() 进行监视并做出相应的处理,此时调用 interrupt() 将不起作用。
下面我们就针对两种 interrupt() 机制写两个例子:
3.1、使用 interrupt() + isInterrupted() 来中断线程:
public static void main(String[] args){
// 创建 interrupt-1 线程
Thread thread = new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getName() + "线程正在执行...");
if (Thread.currentThread().isInterrupted()) {
System.out.println("线程1 接收到中断信息,中断线程...中断标记:" + Thread.currentThread().isInterrupted());
Thread.currentThread().interrupted();
System.out.println("经过 Thread.interrupted() 复位后,中断标记:" + Thread.currentThread().isInterrupted());
break;
}
}
}, "interrupt-1");
// 启动线程 1
thread.start();
//创建 interrupt-2 线程,用于设置线程1的中断状态
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("设置线程中断...." );
thread.interrupt();
},"interrupt-2").start();
}
3.2、使用 interrupt() + InterruptedException 来中断线程:
public static void main(String[] args){
// 创建 interrupt-1 线程
Thread thread = new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getName() + "线程1开始执行...");
// 判断当前线程是否中断,
if (Thread.currentThread().isInterrupted()) {
System.out.println("线程1 接收到中断信息,中断线程...中断标记:" + Thread.currentThread().isInterrupted());
break;
}
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
// 因为抛出InterruptedException异常后,会导致中断标志复位为false,所以再次设置线程的中断状态,也可以直接使用break中断线程
Thread.currentThread().interrupt();
//break;
}
}
System.out.println("线程1执行结果...");
}, "interrupt-1");
// 启动线程 1
thread.start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("设置线程中断...." );
// 用于设置线程1的中断状态
thread.interrupt();
}
从上面的执行结果可以证明,当线程处于阻塞状态时,也是可以感受到中断通知并抛出异常的,所以不用担心长时间休眠中线程感受不到中断了。
总结
对于线程的停止,最正确最优雅的方式就是通过 interrupt() 的方式来实现,但 interrupt() 仅起到通知被停止线程的作用,对被停止的线程而言,它拥有完全的自主权,既可以立即停止,也可以选择一段时间后停止,也可以选择不停止。比如抛出 InterruptedException 后,再次中断设置,让程序能后续继续进行终止操作;也比如线程在进行 IO 操作时,比如写文件操作,这时接收到终止线程的信号,那么它不会立马停止,而是根据自身业务来判断该如何处理,是将整个文件写入成功后再停止还是不停止都取决于被通知线程的处理,因为如果立马终止线程就可能造成数据的不完整性,这并不是业务所不希望的结果。那么可能很多读者会疑惑,既然这样那存在的意义有什么呢,其实是因为对于 Java 而言,就是期望程序之间是能够相互通知、协作的管理线程
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 上周热点回顾(2.17-2.23)
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)