前言
结束一个线程很简单,可以让它正常结束,也可以以中断方式结束,那如何才算优雅呢?
在我看来,如果一个线程能在合理的时间和资源消耗下正常结束那就是最优雅的.这一句话看似简单,但有时却很难实现.因为它不光涉及到线程层面的知识,还包含具体的业务操作.比如,对计算密集型的操作和IO密集型的操作,就需要不同的设计.
对于计算密集型的业务,首先要考虑增加工作者线程和内存,看能否让任务尽快执行完成从而结束线程.这样对代码的改动最小.记住,不要盲目自信! 在你充分了解业务背景和技术细节之前,不要改动代码.很可能你的修改表面上看提升了性能,但是在上线后的第三天却造成了严重的生产事故.这个时候你肯定会说慢点总比现在强.说这些并不是说代码就一定不能动,而是一定审慎,并做好充分的测试,尤其对于一些遗留系统.
对于IO密集型的业务,也得做进一步进行分析: 是阻塞还是非阻塞IO.对于阻塞IO,一定要考虑设置超时时间防止线程长时间无法结束,并记得回收IO资源.上来就增加线程或内存可能会南辕北辙,因为阻塞IO会让线程处于阻塞状态,添加太多线程不会提升系统的响应时间,反而会增加CPU负担.同样,添加内存作用也不大,因为很多时候并不是因为内存使用紧张使得系统响应时间过长.而对于非阻塞IO则需要具体情况具体分析.
说这么多就是让大家树立这样的意识: 所谓优雅一定是建立在你对业务背景和技术细节充分了解的基础上,合适的才是最好的.
实现
因为我们是讲解多线程知识,所以我们从多线程的技术特点出发,看如何优雅地结束线程.网上大部分文章都提到了两种:一种是添加结束标志,另外一种发起中断请求.汪大神还提到了第三种,这种方式兼顾了IO密集操作和计算密集操作.我们先看一下前两种
添加结束标志
所谓添加结束标志,其实就是为线程添加一个volatile类型的布尔变量.当其他线程认为需要结束当前线程时,将该变量设置为true.而当前线程检测到该值为true,则中断当前操作从而结束线程.例如以下代码:
private static volatile boolean stopped=false;
public static void main(String[] args) {
Thread thread=new Thread(()->{
while (!stopped){
ThreadUtil.println(Thread.currentThread(),"I'm running");
ThreadUtil.sleep(3000);
}
ThreadUtil.println(Thread.currentThread(),"I'm stopped");
});
thread.start();
ThreadUtil.sleep(1000);
stopped=true;
}
上面的过程很简单:
- 启动线程thread,该线程内部包含结束标志
stopped - main线程休眠一秒后认为应该结束thread线程了,然后将stopped设置为true
- while循环检测到条件不满足后退出,线程结束 这样,我们就通过添加一个变量来达到中断线程的目的.下面我们看第二种
发起中断请求
所谓发起中断请求就是其他线程调用当前线程的interrupt方法来发送中断信号.那当前线程如何接受这个信号呢? 三种方式:
- 判断自己的isInterrupted()返回值
- 判断Thread.interrupted()返回值
- 捕获InterruptException异常
我们先看一下前两种:
public static void main(String[] args){
Thread thread1=new Thread(){
@Override
public void run() {
while (!this.isInterrupted()){
System.out.println("t1 is running");
}
System.out.println("t1 stopped");
}
};
Thread thread2=new Thread(){
@Override
public void run() {
while (!Thread.interrupted()){
System.out.println("t2 is running");
}
System.out.println("t2 stopped");
}
};
thread1.start();
thread2.start();
ThreadUtil.sleep(1000L);
thread1.interrupt();
thread2.interrupt();
}
过程很简单:
- 声明两个线程 thread1和thread2.thread1通过自身的isInterrupted()接受中断信号,thread2通过线程的静态方法Thread.interrupted()来接受中断信号
- main线程休眠一秒后中断两个线程
- 两个线程退出while循环
上面我们演示了如何通过前两种方式来接受中断信号,下面我们看一下第三种:如何通过捕获
InterruptedException异常来接受中断信号.先撸代码
public static void main(String[] args){
Thread thread=new Thread(){
@Override
public void run() {
//!Thread.interrupted() does not work neither
while (!this.isInterrupted()){
System.out.println("t is running");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
System.out.println("t is interrupted");
}
}
System.out.println("t is stopped");
}
};
thread.start();
ThreadUtil.sleep(1000L);
thread.interrupt();
}
过程也很简单,我们采用catch (InterruptedException e)来接受,当收到中断信号后结束循环并打印t is stopped,方法体中我们让当前线程sleep两秒.但是很遗憾的告诉你,你永远也看不到t is stopped.为什么?
因为thread.interrupt()方法注释中明确说明,如果当前线程包含了wait,join,sleep的相关方法,那么this.isInterrupted()和Thread.interrupted()都会失效,也就是说它们始终返回 FALSE
那怎么修改呢?,很简单,不再采用上面的方式作为判断条件,而是捕获InterruptedException异常后退出循环
public static void main(String[] args){
Thread thread=new Thread(){
@Override
public void run() {
//!Thread.interrupted()
while (true){
System.out.println("t is running");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
System.out.println("t is interrupted");
break;
}
}
System.out.println("t is stopped");
}
};
thread.start();
ThreadUtil.sleep(1000L);
thread.interrupt();
}
上面代码很简单,不再多说.现在我们总结一下
总结
首先我们讲解了如何根据不同的业务场景来选择不同的结束方式,然后从多线程角度描述了关闭线程的两种方式.
- 添加结束标志:通过添加一个volatile的变量来判断是否结束线程
- 通过发送中断请求: 通过捕获中断异常来接受中断信号.期间我们详细说明了三种接受方式,并了解了前两种的弊端.实际工作中我们要避免使用前两种来接受中断信号 下一篇我们重点讲解第三种结束线程的方式.