Java多线程任务超时结束的5种实现方法

在用Java编写并发程序时,往往会碰到某个线程因计算量大或因阻塞而一直处于无响应的情况,我们可能会等的不耐烦(也可能是不想让它占用太多资源)想及时终止掉它,那就需要用到任务超时结束的技巧了。在刚接触到多线程时,我本以为API会提供这样一个多线程类:Thread(Runnable r, long timeout) ,第二个参数用来设置超时时间,可事实并非如此。因为这样的类不具有通用性,面向对象设计语言的目标是达到更高级的抽象,所以系统只提供了更广泛的定时类,及其他一些类方法。这就需要我们借助这些工具来达到任务超时结束的目的。话不多说,直入正题。(PS:由于作者水平有限,这些方法只是给大家提供几个思路,可能并不是教科书式的标准案例)

方法一:使用Thread.join(long million)
(先讲一下本人对join方法的理解,已理解此方法的可以略过)join方法可以这样理解,在理解它之前,先解释另一个常识,即当前线程(后面称为目标线程,因为它是我们想使其超时结束的目标任务)的创建及start的调用,一定是在另一个线程中进行的(最起码是main线程,也可以是不同于main线程的其他线程),这里我们假设为main线程,并且称之为依赖线程,因为目标线程的创建是在他里面执行的。介绍完这些常识就可以进一步解释了,join的字面意思是,使目标线程加入到依赖线程中去,也可以理解为在依赖线程中等待目标线程一直执行直至结束(如果没有设置超时参数的话)。设置了超时参数(假设为5秒)就会这样执行,在依赖线程中调用了join之后,相当于告诉依赖线程,现在我要插入到你的线程中来,即两个线程合二为一,相当于一个线程(如果不执行插入的话,那目标线程和依赖线程就是并行执行),而且目标线程是插在主线程前面,所以目标线程先执行,但你主线程只需要等我5秒,5秒之后,不管我有没有执行完毕,我们两都分开,这时又会变成两个并行执行的线程,而不是目标线程直接结束执行,这点很重要。

其实这个方法比较牵强,因为它主要作用是用来多个线程之间进行同步的。但因为它提供了这个带参数的方法(所以这也给了我们一个更广泛的思路,就是一般带有超时参数的方法我们都可以尝试着用它来实现超时结束任务),所以我们可以用它来实现。注意这里的参数的单位是固定的毫秒,不同于接下来的带单位的函数。具体用法请看示例:

public class JoinTest {
    public static void main(String[] args) {
        Task task1 = new Task("one", 4);
        Task task2 = new Task("two", 2);
        Thread t1 = new Thread(task1);
        Thread t2 = new Thread(task2);
        t1.start();
        try {
            t1.join(2000); // 在主线程中等待t1执行2秒
        } catch (InterruptedException e) {
            System.out.println("t1 interrupted when waiting join");
            e.printStackTrace();
        }
        t1.interrupt(); // 这里很重要,一定要打断t1,因为它已经执行了2秒。
        t2.start();
        try {
            t2.join(1000);
        } catch (InterruptedException e) {
            System.out.println("t2 interrupted when waiting join");
            e.printStackTrace();
        }
    }
}

class Task implements Runnable {
    public String name;
    private int time;

    public Task(String s, int t) {
        name = s;
        time = t;
    }

    public void run() {
        for (int i = 0; i < time; ++i) {
            System.out.println("task " + name + " " + (i + 1) + " round");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println(name
                        + "is interrupted when calculating, will stop...");
                return; // 注意这里如果不return的话,线程还会继续执行,所以任务超时后在这里处理结果然后返回
            }
        }
    }
}


在主线程中等待t1执行2秒之后,要interrupt(而不是直接调用stop,这个方法已经被弃用)掉它,然后在t1里面会产出一个中断异常,在异常里面处理完该处理的事,就要return,一定要return,如果不return的话,t1还会继续执行,只不过是与主线程并行执行。

方法二:Future.get(long million, TimeUnit unit) 配合Future.cancle(true)
Future系列(它的子类)的都可以实现,这里采用最简单的Future接口实现。

public class FutureTest {
    static class Task implements Callable<Boolean> {
        public String name;
        private int time;

        public Task(String s, int t) {
            name = s;
            time = t;
        }

        @Override
        public Boolean call() throws Exception {
            for (int i = 0; i < time; ++i) {
                System.out.println("task " + name + " round " + (i + 1));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println(name
                            + " is interrupted when calculating, will stop...");
                    return false; // 注意这里如果不return的话,线程还会继续执行,所以任务超时后在这里处理结果然后返回
                }
            }
            return true;
        }
    }

    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task1 = new Task("one", 5);
        Future<Boolean> f1 = executor.submit(task1);
        try {
            if (f1.get(2, TimeUnit.SECONDS)) { // future将在2秒之后取结果
                System.out.println("one complete successfully");
            }
        } catch (InterruptedException e) {
            System.out.println("future在睡着时被打断");
            executor.shutdownNow();
        } catch (ExecutionException e) {
            System.out.println("future在尝试取得任务结果时出错");
            executor.shutdownNow();
        } catch (TimeoutException e) {
            System.out.println("future时间超时");
            f1.cancel(true);
            // executor.shutdownNow();
            // executor.shutdown();
        } finally {
            executor.shutdownNow();
        }
    }
}



运行结果如下,task在2秒之后停止: 
 
如果把Task中捕获InterruptedException的catch块中的return注释掉,就是这样的结果: 
 
task继续执行,直至结束

方法三:ExecutorService.awaitTermination(long million, TimeUnit unit)
这个方法会一直等待所有的任务都结束,或者超时时间到立即返回,若所有任务都完成则返回true,否则返回false

public class AwaitTermination {
    static class Task implements Runnable {
        public String name;
        private int time;

        public Task(String s, int t) {
            name = s;
            time = t;
        }

        public void run() {
            for (int i = 0; i < time; ++i) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println(name
                            + " is interrupted when calculating, will stop...");
                    return; // 注意这里如果不return的话,线程还会继续执行,所以任务超时后在这里处理结果然后返回
                }
                System.out.println("task " + name + " " + (i + 1) + " round");
            }
            System.out.println("task " + name + " finished successfully");
        }
    }

    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task("one", 5);
        Task task2 = new Task("two", 2);
        Future<?> future = executor.submit(task);
        Future<?> future2 = executor.submit(task2);
        List<Future<?>> futures = new ArrayList<Future<?>>();
        futures.add(future);
        futures.add(future2);
        try {
            if (executor.awaitTermination(3, TimeUnit.SECONDS)) {
                System.out.println("task finished");
            } else {
                System.out.println("task time out,will terminate");
                for (Future<?> f : futures) {
                    if (!f.isDone()) {
                        f.cancel(true);
                    }
                }
            }
        } catch (InterruptedException e) {
            System.out.println("executor is interrupted");
        } finally {
            executor.shutdown();
        }
    }
}

 


运行结果如下: 


方法四:设置一个守护线程,守护线程先sleep一段定时时间,睡醒后打断它所监视的线程
public class DemonThread {
    static class Task implements Runnable {
        private String name;
        private int time;

        public Task(String s, int t) {
            name = s;
            time = t;
        }

        public int getTime() {
            return time;
        }

        public void run() {
            for (int i = 0; i < time; ++i) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println(name
                            + " is interrupted when calculating, will stop...");
                    return; // 注意这里如果不return的话,线程还会继续执行,所以任务超时后在这里处理结果然后返回
                }
                System.out.println("task " + name + " " + (i + 1) + " round");
            }
            System.out.println("task " + name + " finished successfully");
        }
    }

    static class Daemon implements Runnable {
        List<Runnable> tasks = new ArrayList<Runnable>();
        private Thread thread;
        private int time;

        public Daemon(Thread r, int t) {
            thread = r;
            time = t;
        }

        public void addTask(Runnable r) {
            tasks.add(r);
        }

        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(time * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                thread.interrupt();
            }
        }

    }

    public static void main(String[] args) {
        Task task1 = new Task("one", 5);
        Thread t1 = new Thread(task1);
        Daemon daemon = new Daemon(t1, 3);
        Thread daemoThread = new Thread(daemon);
        daemoThread.setDaemon(true);
        t1.start();
        daemoThread.start();
    }
}

 


一开始准备在守护任务里面用一个集合来实现监视多个任务,接着发现要实现这个功能还得在这个守护任务里面为每一个监视的任务开启一个监视任务,一时又想不到更好的方法来解决,索性只监视一个算了,留待以后改进吧。 
运行结果如下: 


方法五:使用Timer / TimerTask,或其他schedule定时相关的类
总结:需要注意的是,无论以上哪一种方法,其实现原理都是在超时后通过interrupt打断目标线程的运行,所以都要在捕捉到InterruptedException的catch代码块中return,否则线程仍然会继续执行。另外,最后两种方法本质上是一样的,都是通过持有目标线程的引用,在定时结束后打断目标线程,这两种方法的控制精度最低,因为它是采用另一个线程来监视目标线程的运行时间,因为线程调度的不确定性,另一个线程在定时结束后不一定会马上得到执行而打断目标线程。
--------------------- 
作者:won-king 
来源:CSDN 
原文:https://blog.csdn.net/wonking666/article/details/76552019 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。 XXL-JOB特点: 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手; 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效; 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA; 4、执行器HA(分布式):任务分布式执行任务"执行器"支持集群部署,可保证任务执行HA; 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址; 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务; 7、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等; 8、故障转移:任务路由策略选择"故障转移"情况下,如果执行器集群中某一台机器故障,将会自动Failover切换到一台正常的执行器发送调度请求。 9、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度; 10、任务超时控制:支持自定义任务超时时间,任务运行超时将会主动中断任务; 11、任务失败重试:支持自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试;其中分片任务支持分片粒度的失败重试; 12、任务失败告警;默认提供邮件方式失败告警,同时预留扩展接口,可方便的扩展短信、钉钉等告警方式; 13、分片广播任务执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行执行一次任务,可根据分片参数开发分片任务; 14、动态分片:分片广播任务执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。 15、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。 16、任务进度监控:支持实时监控任务进度; 17、Rolling实时日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志; 18、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。 19、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python、NodeJS、PHP、PowerShell等类型脚本; 20、命令行任务:原生提供通用命令行任务Handler(Bean任务,"CommandJobHandler");业务方只需要提供命令行即可; 21、任务依赖:支持配置子任务依赖,当父任务执行结束执行成功后将会主动触发一次子任务执行, 多个子任务用逗号分隔; 22、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行; 23、自定义任务参数:支持在线配置调度任务入参,即时生效; 24、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞; 25、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性; 26、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件; 27、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用; 28、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等; 29、全异步:任务调度流程全异步化设计实现,如异步调度、异步运行、异步回调等,有效对密集调度进行流量削峰,理论上支持任意时长任务的运行; 30、跨语言:调度中心与执行器提供语言无关的 RESTful API 服务,第三方任意语言可据此对接调度中心或者实现执行器。除此之外,还提供了 “多任务模式”和“httpJobHandler”等其他跨语言方案; 31、国际化:调度中心支持国际化设置,提供中文、英文两可选语言,默认为中文; 32、容器化:提供官方docker镜像,并实时更新推送dockerhub,进一步实现产品开箱即用; 33、线程池隔离:调度线程池进行隔离拆分,慢任务自动降级进入"Slow"线程池,避免耗尽调度线程,提高系统稳定性; 34、用户管理:支持在线管理系统用户,存在管理员、普通用户两角色; 35、权限控
解决线程的死掉问题和超时问题特别好使,在Java中,如果需要设定代码执行的最长时间,即超时,可以用Java线程池ExecutorService类配合Future接口来实现。 Future接口是Java标准API的一部分,在java.util.concurrent包中。Future接口是Java线程Future模式的实 现,可以来进行异步计算。 Future模式可以这样来描述:我有一个任务,提交给了Future,Future替我完成这个任务。期间我自己可以去做任何想做的事情。一段时 间之后,我就便可以从Future那儿取出结果。就相当于下了一张订货单,一段时间后可以拿着提订单来提货,这期间可以干别的任何事情。其中Future 接口就是订货单,真正处理订单的是Executor类,它根据Future接口的要求来生产产品。 Future接口提供方法来检测任务是否被执行完,等待任务执行完获得结果,也可以设置任务执行超时时间。这个设置超时方法就是实现Java程 序执行超时的关键。 Future接口是一个泛型接口,严格的格式应该是Future,其中V代表了Future执行任务返回值的类型。 Future接口的方法介绍如下: boolean cancel(boolean mayInterruptIfRunning) 取消任务执行。参数指定是否立即中断任务执行,或者等等任务结束 boolean isCancelled() 任务是否已经取消,任务正常完成前将其取消,则返回true boolean isDone() 任务是否已经完成。需要注意的是如果任务正常终止、异常或取消,都将返回true V get() throws InterruptedException, ExecutionException 等待任务执行结束,然后获得V类型的结果。InterruptedException 线程被中断异常, ExecutionException任务执行异常,如果任务被取消,还会抛出CancellationException V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException 同上面的get功能一样,多了设置超时时间。参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。如果计 算超时,将抛出TimeoutException Future的实现类有java.util.concurrent.FutureTask即 javax.swing.SwingWorker。通常使用FutureTask来处理我们的任务。FutureTask类同时又 实现了Runnable接口,所以可以直接提交给Executor执行。使用FutureTask实现超时执行的代码如附件:FutureTaskAndExcutor.java 不直接构造Future对象,也可以使用ExecutorService.submit方法来获得Future对象,submit方法即支持以 Callable接口类型,也支持Runnable接口作为参数,具有很大的灵活性。使用示例如FutureTaskAndExcutor中的limitDemo2方法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值