# 线程超时

任务超时处理是比较常见的需求，比如在进行一些比较耗时的操作（如网络请求）或者在占用一些比较宝贵的资源（如数据库连接）时，我们通常需要给这些操作设置一个超时时间，当执行时长超过设置的阈值的时候，就终止操作并回收资源。看了很多文章的处理方式，基本都是通过调用线程的interrupt方法，触发一个中断异常，线程捕获到中断异常之后`return`。代码层面，基于这种思想有几种比较有代表性的处理方式：`基于线程的join(long millis)方法` `Future.get(long million, TimeUnit unit) 配合Future.cancle(true)` `定时器或者守护线程`

### 1. 基于线程的join(long millis)方法

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

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

### 2. Future.get(long million, TimeUnit unit) 配合Future.cancle(true)

```java
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();
        }
    }
}
```

### 3. 定时器或者守护线程

这两种方法本质上是一样的，都是通过持有目标线程的引用，在定时结束后打断目标线程，这两种方法的控制精度最低，因为它是采用另一个线程来监视目标线程的运行时间，因为线程调度的不确定性，另一个线程在定时结束后不一定会马上得到执行而打断目标线程。

总结：需要注意的是，无论以上哪一种方法，其实现原理都是在超时后通过interrupt打断目标线程的运行，所以都要在捕捉到InterruptedException的catch代码块中return,否则线程仍然会继续执行。

#### 参考链接：

[Java多线程任务超时结束的5种实现方法](https://blog.csdn.net/wonking666/article/details/76552019)
