CompletableFuture的正常,异常,timeout和cancel

背景

Java 8的CompletableFuture提供了强大的task管理能力,比如通知机制,以及更加简单抽象的task管理。

本文中,将用CompletableFuture的 supplyAsync() 方法来创建一个异步task,task会完成特定的工作,并且返回一个String。

下面的 handleResult() 方法作为task完成时的回调函数(注意:task完成并不意味着task的实际工作一定已经运行结束了,比如timeout和cancel场景)。

    public static void handleResult(String result) {
        System.out.println("==========result: " + result + "===========");
    }

一般来说,task的完成,有以下几种原因:

  • 正常结束
  • 异常结束
  • timeout
  • cancel

本文将以代码示例,如何处理task的不同完成状态。

注:本文中使用的有些方法(比如 completeOnTimeout() ),是Java 9提供的。

单独流程

正常流程

使用 thenAccept() 方法来处理task结果,代码如下:

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "hello");

        future.thenAccept(e -> handleResult(e));
        
        try {
            Thread.sleep(10* 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

注:在主线程里面sleep一段时间,其目的是确保task在主线程结束前完成工作。

运行结果如下:

==========result: hello===========

Process finished with exit code 0

异常流程

使用 exceptionally() 方法来处理task的异常,代码如下:

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            throw new RuntimeException("xxx");
        });

        // future.thenAccept(e -> handleResult(e)); // will not take effect

        future.exceptionally(e -> {
            System.out.println("got exception: " + e.getClass() + ", " + e.getCause());
            handleResult("default exception result");
            return "default exception result";
        });

        try {
            Thread.sleep(10* 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

运行结果如下:

got exception: class java.util.concurrent.CompletionException, java.lang.RuntimeException: xxx
==========result: default exception result===========

正常和异常流程合并处理

使用 handle() 方法,它有2个参数:task结果和task异常,代码如下:

        future.handle((r, e) -> {
            if (e != null) {
                System.out.println("got exception: " + e.getClass() + ", " + e.getCause());
                handleResult("default exception result");
                return "default exception result";
            } else {
                handleResult(r);
                return r;
            }
        });

timeout流程

使用 completeOnTimeout() 方法,设置timeout时间,并且在timeout发生时指定task结果。代码如下:

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello";
        });

        future.completeOnTimeout("default timeout result", 3 * 1000, TimeUnit.MILLISECONDS);

        future.handle((r, e) -> {
            if (e != null) {
                System.out.println("got exception: " + e.getClass() + ", " + e.getCause());
                handleResult("default exception result");
                return "default exception result";
            } else {
                handleResult(r);
                return r;
            }
        });

        try {
            Thread.sleep(10* 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

运行结果如下:

==========result: default timeout result===========

注意:在 handle() 方法里走的是正常流程,而不是异常流程。

注:也可以用 orTimeout() 方法,指定timeout时间,则在timeout发生时,task会抛出 TimeoutException 异常。

注意:timeout并不会真正停止task的运行,也不会给task发interrupt信号。

cancel流程

使用 cancel() 方法来cancel task,并抛出 CancellationException 异常。代码如下:

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello";
        });

        future.handle((r, e) -> {
            if (e != null) {
                System.out.println("got exception: " + e.getClass() + ", " + e.getCause());
                handleResult("default exception result");
                return "default exception result";
            } else {
                handleResult(r);
                return r;
            }
        });

        try {
            Thread.sleep(3 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        future.cancel(true);

        try {
            Thread.sleep(10* 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

运行结果如下:

got exception: class java.util.concurrent.CancellationException, null
==========result: default exception result===========

注意:cancel操作的触发时机不可知,一般是在类之外被触发的,所以本例中把cancel操作放在了最后(前面都是对future的基本操作)。

注意:cancel时,在 handle() 方法里走的是异常流程,其Exception为 CancellationException

注意:timeout并不会真正停止task的运行,也不会给task发interrupt信号。

合并流程

现在,考虑所有4种情况,把逻辑处理合到一起,代码如下:

package com.example.test0721;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class Test0724 {
    public static void handleResult(String result) {
        System.out.println("============result: " + result + "=============");
    }

    public static void main(String[] args) {
        Logger log = LoggerFactory.getLogger(Test0724.class);

        log.info("main: started");

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            log.info("async: started");
            try {
                Thread.sleep(10 * 1000);
            } catch (InterruptedException e) {
                log.info("async: interrupted");
                return "default interrupted result";
            }

            boolean exceptional = false;
            if (exceptional) {
                log.info("async: run into exception");
                throw new RuntimeException("async exception");
            } else {
                log.info("async: finished");
                return "hello";
            }
        }).completeOnTimeout("default timeout result", 100 * 1000, TimeUnit.MILLISECONDS);

        // attention: split the line, because the cancel operation is against the above "future"
        future.handle((result, throwable) -> {
            log.info("async: result: " + result + ", throwable: " + throwable);
            if (throwable != null) {
                log.info("async: got exception from async: " + throwable.getClass() + ", " + throwable.getCause());
                handleResult("default exception result");
                return "default exception result";
            } else {
                log.info("got normal result: " + result);
                handleResult(result);
                return result;
            }
        });

//        try {
//            Thread.sleep(2 * 1000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
//
//        log.info("default cancel value");
//        future.cancel(true);
//        log.info("main: async cancelled");

        try {
            Thread.sleep(15 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        log.info("main ended");
    }
}

正常流程

直接运行代码,就是正常流程,运行结果如下:

02:33:39.239 [main] INFO com.example.test0721.Test0724 - main: started
02:33:39.259 [ForkJoinPool.commonPool-worker-3] INFO com.example.test0721.Test0724 - async: started
02:33:49.265 [ForkJoinPool.commonPool-worker-3] INFO com.example.test0721.Test0724 - async: finished
02:33:49.298 [ForkJoinPool.commonPool-worker-3] INFO com.example.test0721.Test0724 - async: result: hello, throwable: null
02:33:49.299 [ForkJoinPool.commonPool-worker-3] INFO com.example.test0721.Test0724 - got normal result: hello
============result: hello=============
02:33:54.268 [main] INFO com.example.test0721.Test0724 - main ended

异常流程

exceptional 变量设置为 true

            boolean exceptional = true;

运行结果如下:

02:34:23.351 [main] INFO com.example.test0721.Test0724 - main: started
02:34:23.368 [ForkJoinPool.commonPool-worker-3] INFO com.example.test0721.Test0724 - async: started
02:34:33.371 [ForkJoinPool.commonPool-worker-3] INFO com.example.test0721.Test0724 - async: run into exception
02:34:33.410 [ForkJoinPool.commonPool-worker-3] INFO com.example.test0721.Test0724 - async: result: null, throwable: java.util.concurrent.CompletionException: java.lang.RuntimeException: async exception
02:34:33.411 [ForkJoinPool.commonPool-worker-3] INFO com.example.test0721.Test0724 - async: got exception from async: class java.util.concurrent.CompletionException, java.lang.RuntimeException: async exception
============result: default exception result=============
02:34:38.373 [main] INFO com.example.test0721.Test0724 - main ended

timeout流程

把task的timeout时间设置为5秒钟:

                Thread.sleep(5 * 1000);

运行结果如下:

02:35:27.985 [main] INFO com.example.test0721.Test0724 - main: started
02:35:27.996 [ForkJoinPool.commonPool-worker-3] INFO com.example.test0721.Test0724 - async: started
02:35:33.040 [CompletableFutureDelayScheduler] INFO com.example.test0721.Test0724 - async: result: default timeout result, throwable: null
02:35:33.042 [CompletableFutureDelayScheduler] INFO com.example.test0721.Test0724 - got normal result: default timeout result
============result: default timeout result=============
02:35:37.999 [ForkJoinPool.commonPool-worker-3] INFO com.example.test0721.Test0724 - async: run into exception
02:35:43.007 [main] INFO com.example.test0721.Test0724 - main ended

注意:timeout并不会真正停止task的运行,也不会给task发interrupt信号。本例中,由于把 exception 设置为 true ,可以看到console有 async: run into exception 的输出。同理,假设task没有抛异常,则最终将会在console显示 async: finished

cancel流程

把下面的代码反注释:

        try {
            Thread.sleep(2 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        log.info("default cancel value");
        future.cancel(true);
        log.info("main: async cancelled");
        handleResult("default cancel value");

即:在task运行到2秒钟的时候,cancel task。运行结果如下:

02:41:03.815 [main] INFO com.example.test0721.Test0724 - main: started
02:41:03.841 [ForkJoinPool.commonPool-worker-3] INFO com.example.test0721.Test0724 - async: started
02:41:05.841 [main] INFO com.example.test0721.Test0724 - default cancel value
02:41:05.870 [main] INFO com.example.test0721.Test0724 - async: result: null, throwable: java.util.concurrent.CancellationException
02:41:05.871 [main] INFO com.example.test0721.Test0724 - async: got exception from async: class java.util.concurrent.CancellationException, null
============result: default exception result=============
02:41:05.875 [main] INFO com.example.test0721.Test0724 - main: async cancelled
02:41:13.842 [ForkJoinPool.commonPool-worker-3] INFO com.example.test0721.Test0724 - async: run into exception
02:41:20.876 [main] INFO com.example.test0721.Test0724 - main ended

注意:cancel并不会真正停止task的运行,也不会给task发interrupt信号。本例中,由于把 exception 设置为 true ,可以看到console有 async: run into exception 的输出。同理,假设task没有抛异常,则最终将会在console显示 async: finished

注意:本例中没有区分task自身的异常和cancel task造成的异常。若想取分的话,只需在 handle() 方法里对异常加以判断。timeout异常也同理。

你可能感兴趣的:(Java,多线程,java,多线程)