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的话,线程还会继续执行,所以任务超时后在这里处理结果然后返回
        }
    }
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
在主线程中等待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 {
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();
    }
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
运行结果如下,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();
    }
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
运行结果如下:

方法四:设置一个守护线程,守护线程先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();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
一开始准备在守护任务里面用一个集合来实现监视多个任务,接着发现要实现这个功能还得在这个守护任务里面为每一个监视的任务开启一个监视任务,一时又想不到更好的方法来解决,索性只监视一个算了,留待以后改进吧。
运行结果如下:

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

解决线程的死掉问题和超时问题特别好使,在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、付费专栏及课程。

余额充值