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

方法一:使用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,否则线程仍然会继续执行。另外,最后两种方法本质上是一样的,都是通过持有目标线程的引用,在定时结束后打断目标线程,这两种方法的控制精度最低,因为它是采用另一个线程来监视目标线程的运行时间,因为线程调度的不确定性,另一个线程在定时结束后不一定会马上得到执行而打断目标线程。

 

原文:https://blog.csdn.net/wonking666/article/details/76552019

posted @ 2020-03-20 09:17    阅读(4229)  评论(0编辑  收藏  举报