java Future多个任务的超时时间问题

问题

    public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(() -> "123");
        String s = future.get(1, TimeUnit.SECONDS);
        System.out.println(s);
    }

  上图是一段简单代码,表示最多等待一秒钟获取任务执行结果,否则超时,但这个超时时间是从什么时候开始计算的呢?

public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        System.out.println(LocalDateTime.now());
        Future<String> future = executor.submit(() -> {
            TimeUnit.MINUTES.sleep(1);
            return "123";
        });
        TimeUnit.SECONDS.sleep(3);
        System.out.println(LocalDateTime.now());
        String s = null;
        try {
            s = future.get(1, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            System.out.println(LocalDateTime.now());
        }
    }
2024-01-23T16:11:26.388319
2024-01-23T16:11:29.392559
2024-01-23T16:11:30.394918

  通过这个示例,可以看到这个超时时间并不是从submit这个任务的时候开始的,而是在get的时候开始计算等待时间,如果有多个任务的时候,程序并不能像我以为的那样运行。例如如下

public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        System.out.println(LocalDateTime.now());
        
        List<Future<String>> futures = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Future<String> future = executor.submit(() -> {
                TimeUnit.MINUTES.sleep(1);
                return "123";
            });
            futures.add(future);
        }
        for (Future<String> future : futures) {
            try {
                String s = future.get(1, TimeUnit.SECONDS);
            } catch (TimeoutException e) {
                System.out.println(LocalDateTime.now());
            }
        }
    }
2024-01-23T16:21:34.607510
2024-01-23T16:21:35.611482
2024-01-23T16:21:36.611936
2024-01-23T16:21:37.612215
2024-01-23T16:21:38.613503
2024-01-23T16:21:39.613871

  我预期的是添加5个任务,然后最多等待1秒返回,但因为超时时间是按照get的时间开始,所以整个流程足足等待了5秒

 

解决方案

  方案1 使用invokeAll方法

public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(1024);
        System.out.println(LocalDateTime.now());

        List<Callable<String>> callables = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            callables.add(()->{
                TimeUnit.MINUTES.sleep(1);
                return "123";
            });
        }
        System.out.println(LocalDateTime.now());
        List<Future<String>> futures = executor.invokeAll(callables,3,TimeUnit.SECONDS);
        for (Future<String> future : futures) {
            try {
                String s = future.get();
            } catch (Exception e){
                System.out.println("超时"+LocalDateTime.now());
            }
        }

    }
2024-01-23T16:35:14.700841
2024-01-23T16:35:14.702355
超时2024-01-23T16:35:17.704613
超时2024-01-23T16:35:17.707435
超时2024-01-23T16:35:17.707512
超时2024-01-23T16:35:17.707588
超时2024-01-23T16:35:17.707655

  可以看到使用线程池的invoke方法后,所有任务都在3秒后快速超时。

  方法优点:简单粗暴,能符合大部分时候的需求

  方法缺点:对多个任务返回值不同时不太友好;多个任务无法独立设置超时时间

 

  方案2 使用CompletableFuture.allOf()

    public static void main(String[] args) throws InterruptedException {
        System.out.println(LocalDateTime.now());
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
            }
            return "123";
        });
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
            }
            return 123;
        });

        System.out.println(LocalDateTime.now());
        try {
            CompletableFuture.allOf(future1,future2).get(1,TimeUnit.SECONDS);
        }  catch (ExecutionException | TimeoutException e) {
        }
        if (future1.isDone()){
            try {
                future1.get();
                System.out.println(LocalDateTime.now());
            } catch (ExecutionException e) {
            }
        }else {
            System.out.println(LocalDateTime.now());
        }
        if (future2.isDone()){
            try {
                future2.get();
                System.out.println(LocalDateTime.now());
            } catch (ExecutionException e) {
            }
        }else {
            System.out.println(LocalDateTime.now());
        }
    }
2024-01-23T17:42:39.389196
2024-01-23T17:42:39.393157
2024-01-23T17:42:40.394024
2024-01-23T17:42:40.394156

  方法优点:简单粗暴,能符合大部分时候的需求

  方法缺点:多个任务无法独立设置超时时间

  如果用的java8以下可以用CountDownLatch代替,在各个任务的finaly里面countDown,将await的等待时间作为超时时间即可。

 

  方案3 使用CompletableFuture.orTimeout

public static void main(String[] args) throws InterruptedException {
        System.out.println(LocalDateTime.now());
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.MINUTES.sleep(1);
            } catch (InterruptedException e) {
            }
            return "123";
        }).orTimeout(1,TimeUnit.SECONDS);
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.MINUTES.sleep(1);
            } catch (InterruptedException e) {
            }
            return 123;
        }).orTimeout(2,TimeUnit.SECONDS);

        System.out.println(LocalDateTime.now());
        try {
            future1.get();
        }  catch (ExecutionException e) {
            System.out.println(LocalDateTime.now());
        }
        try {
            future2.get();
        }  catch (ExecutionException e) {
            System.out.println(LocalDateTime.now());
        }
    }
2024-01-23T17:45:27.526374
2024-01-23T17:45:27.533786
2024-01-23T17:45:28.535025
2024-01-23T17:45:29.534531

  方法优点:基本完美符合各种需求

  方法缺点:该方法从JAVA9开始提供(手动狗头) 

posted @ 2024-01-23 18:17  雨落寒沙  阅读(1332)  评论(0)    收藏  举报