正确结束Java线程的方法

 更新时间:2019年05月31日 15:07:43   作者:展翅而飞  
线程的启动很简单,但用户可能随时取消任务,怎么样让跑起来的线程正确地结束,这是今天要讨论的话题。下面小编来和大家一起学习一下吧

使用标志位

很简单地设置一个标志位,名称就叫做isCancelled。启动线程后,定期检查这个标志位。如果isCancelled=true,那么线程就马上结束。

public class MyThread implements Runnable{
private volatile boolean isCancelled;
public void run(){
while(!isCancelled){
//do something
}
}
public void cancel(){ isCancelled=true; }
}

注意的是,isCancelled需要为volatile,保证线程读取时isCancelled是最新数据。
我以前经常用这种简单方法,在大多时候也很有效,但并不完善。考虑下,如果线程执行的方法被阻塞,那么如何执行isCancelled的检查呢?线程有可能永远不会去检查标志位,也就卡住了。

使用中断

Java提供了中断机制,Thread类下有三个重要方法。

  • public void interrupt()
  • public boolean isInterrupted()
  • public static boolean interrupted(); // 清除中断标志,并返回原状态

每个线程都有个boolean类型的中断状态。当使用Thread的interrupt()方法时,线程的中断状态会被设置为true。
下面的例子启动了一个线程,循环执行打印一些信息。使用isInterrupted()方法判断线程是否被中断,如果是就结束线程。

public class InterruptedExample {
public static void main(String[] args) throws Exception {
InterruptedExample interruptedExample = new InterruptedExample();
interruptedExample.start();
}
public void start() {
MyThread myThread = new MyThread();
myThread.start();
try {
Thread.sleep(3000);
myThread.cancel();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private class MyThread extends Thread{
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("test");
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("interrupt");
//抛出InterruptedException后中断标志被清除,标准做法是再次调用interrupt恢复中断
Thread.currentThread().interrupt();
}
}
System.out.println("stop");
}
public void cancel(){
interrupt();
}
}
}

对线程调用interrupt()方法,不会真正中断正在运行的线程,只是发出一个请求,由线程在合适时候结束自己。

例如Thread.sleep这个阻塞方法,接收到中断请求,会抛出InterruptedException,让上层代码处理。这个时候,你可以什么都不做,但等于吞掉了中断。因为抛出InterruptedException后,中断标记会被重新设置为false!看sleep()的注释,也强调了这点。

@throws InterruptedException
if any thread has interrupted the current thread. 
The interrupted status of the current thread is 
cleared when this exception is thrown.
public static native void sleep(long millis) throws InterruptedException;

记得这个规则:什么时候都不应该吞掉中断!每个线程都应该有合适的方法响应中断!

所以在InterruptedExample例子里,在接收到中断请求时,标准做法是执行Thread.currentThread().interrupt()恢复中断,让线程退出。

从另一方面谈起,你不能吞掉中断,也不能中断你不熟悉的线程。如果线程没有响应中断的方法,你无论调用多少次interrupt()方法,也像泥牛入海。

用Java库的方法比自己写的要好

自己手动调用interrupt()方法来中断程序,OK。但是Java库提供了一些类来实现中断,更好更强大。

Executor框架提供了Java线程池的能力,ExecutorService扩展了Executor,提供了管理线程生命周期的关键能力。其中,ExecutorService.submit返回了Future对象来描述一个线程任务,它有一个cancel()方法。

下面的例子扩展了上面的InterruptedExample,要求线程在限定时间内得到结果,否则触发超时停止。

public class InterruptByFuture {
public static void main(String[] args) throws Exception {
ExecutorService es = Executors.newSingleThreadExecutor();
Future<?> task = es.submit(new MyThread());
try {
//限定时间获取结果
task.get(5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
//超时触发线程中止
System.out.println("thread over time");
} catch (ExecutionException e) {
throw e;
} finally {
boolean mayInterruptIfRunning = true;
task.cancel(mayInterruptIfRunning);
}
}
private static class MyThread extends Thread {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) { 
try {
System.out.println("count");
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("interrupt");
Thread.currentThread().interrupt();
}
}
System.out.println("thread stop");
}
public void cancel() {
interrupt();
}
}
}

Future的get方法可以传入时间,如果限定时间内没有得到结果,将会抛出TimeoutException。此时,可以调用Future的cancel()方法,对任务所在线程发出中断请求。
cancel()有个参数mayInterruptIfRunning,表示任务是否能够接收到中断。

  • mayInterruptIfRunning=true时,任务如果在某个线程中运行,那么这个线程能够被中断;
  • mayInterruptIfRunning=false时,任务如果还未启动,就不要运行它,应用于不处理中断的任务

要注意,mayInterruptIfRunning=true表示线程能接收中断,但线程是否实现了中断不得而知。线程要正确响应中断,才能真正被cancel。

线程池的shutdownNow()会尝试停止池内所有在执行的线程,原理也是发出中断请求。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • 如何使用hutool做本地缓存的工具类

    如何使用hutool做本地缓存的工具类

    这篇文章主要介绍了如何使用hutool做本地缓存的工具类,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • idea配置检查XML中SQL语法及书写sql语句智能提示的方法

    idea配置检查XML中SQL语法及书写sql语句智能提示的方法

    idea连接了数据库,也可以执行SQL查到数据,但是无法识别sql语句中的表导致没有提示,下面这篇文章主要给大家介绍了关于idea配置检查XML中SQL语法及书写sql语句智能提示的相关资料,需要的朋友可以参考下
    2023-03-03
  • Spring Cloud实现5分钟级区域切换的操作方法

    Spring Cloud实现5分钟级区域切换的操作方法

    Spring Cloud 2023.x通过智能路由预热、多活数据同步和自动化流量切换,实现5分钟内完成跨区域故障转移,本文以某电商平台从AWS亚太切换至阿里云华东的实战为例,详解关键技术路径,需要的朋友可以参考下
    2025-04-04
  • jquery uploadify和apache Fileupload实现异步上传文件示例

    jquery uploadify和apache Fileupload实现异步上传文件示例

    这篇文章主要介绍了jquery uploadify和apache Fileupload实现异步上传文件示例,需要的朋友可以参考下
    2014-05-05
  • 在Java中使用日志框架log4j的方法

    在Java中使用日志框架log4j的方法

    Log4j有三个主要的组件/对象:Loggers(记录器),Appenders (输出源)和Layouts(布局)。这里可简单理解为日志类别,日志要输出的地方和日志以何种形式输出,今天通过本文给大家分享Java日志框架log4j的相关知识,感兴趣的朋友一起看看吧
    2021-08-08
  • Java之OutputStreamWriter流案例详解

    Java之OutputStreamWriter流案例详解

    这篇文章主要介绍了Java之OutputStreamWriter流案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • Java中注解@JsonFormat与@DateTimeFormat的使用

    Java中注解@JsonFormat与@DateTimeFormat的使用

    从数据库获取时间传到前端进行展示的时候,我们有时候可能无法得到一个满意的时间格式的时间日期,本文主要介绍了Java中注解@JsonFormat与@DateTimeFormat的使用,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2023-08-08
  • Java Spring事务的隔离级别详解

    Java Spring事务的隔离级别详解

    这篇文章主要介绍了Java Spring事务的隔离级别,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2021-10-10
  • Java处理Webp图片格式转换的示例代码

    Java处理Webp图片格式转换的示例代码

    这篇文章主要介绍了Java处理Webp图片格式转换的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • SpringBoot如何集成PageHelper分页功能

    SpringBoot如何集成PageHelper分页功能

    这篇文章主要介绍了SpringBoot如何集成PageHelper分页功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03

最新评论