Future的异常处理以及CompletableFuture传播链、异常处理

Future.get(long, TimeUnit)

java 复制代码
	/**
     * Waits if necessary for at most the given time for the computation
     * to complete, and then retrieves its result, if available.
     *
     * @param timeout the maximum time to wait
     * @param unit the time unit of the timeout argument
     * @return the computed result
     * @throws CancellationException if the computation was cancelled
     * @throws ExecutionException if the computation threw an
     * exception
     * @throws InterruptedException if the current thread was interrupted
     * while waiting
     * @throws TimeoutException if the wait timed out
     */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;

从方法签名看,会抛出三种异常,注释中又多出一种超时异常。

FutureTask.get(long, TimeUnit)和CompletableFuture.get(long, TimeUnit)都实现了Future.get(long, TimeUnit)方法,他们的异常行为是一致的

异常类型 是否受检 异常原因
InterruptedException 执行get的线程被中断(不是任务线程被中断)
TimeoutException 任务执行超时
CancellationException 任务完成前被cancel
ExecutionException 其他异常都会被包装成ExecutionException,真实异常通过getCause获取

如果使用了cancel操作,要记得处理CancellationException

java 复制代码
public static void main(String[] args)  {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Future<String> future = executorService.submit(() -> {
            Thread.sleep(100);
            return "";
        });
        future.cancel(true);
        try {
            future.get(10, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            System.out.println("InterruptedException");
        } catch (ExecutionException e) {
            System.out.println("ExecutionException");
        } catch (TimeoutException e) {
            System.out.println("TimeoutException");
        } catch (CancellationException e) {
            System.out.println("CancellationException");
        } finally {
            executorService.shutdown();
        }
    }

捕获ExecutionException,并得到真实异常

java 复制代码
public static void main(String[] args)  {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Future<String> future = executorService.submit(() -> {
            throw new RuntimeException("Err Msg");
        });
        try {
            future.get(10, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            System.out.println("InterruptedException");
        } catch (ExecutionException e) {
            System.out.println("ExecutionException " + e.getCause());
        } catch (TimeoutException e) {
            System.out.println("TimeoutException");
        } catch (CancellationException e) {
            System.out.println("CancellationException");
        } finally {
            executorService.shutdown();
        }
    }

捕获InterruptedException。捕获InterruptedException后,抛出InterruptedException的线程的中断标识会被重置,所以需要手动设置Thread.currentThread().interrupt();

java 复制代码
public static void main(String[] args)  {
        ExecutorService executorService = Executors.newFixedThreadPool(1);

        Thread mainThread = Thread.currentThread();

        Future<String> future = executorService.submit(() -> {
            Thread.sleep(1000);
            return "ok";
        });

        // 中断执行get的线程
        new Thread(() -> {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("mainThread before " + mainThread.isInterrupted());
            mainThread.interrupt();
            System.out.println("mainThread after " + mainThread.isInterrupted());
        }).start();

        try {
            future.get(1000, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            System.out.println("InterruptedException mainThread before " + mainThread.isInterrupted());
            // InterruptedException会清除中断标记,生产代码通常需要恢复
            Thread.currentThread().interrupt();
            System.out.println("InterruptedException mainThread after " + mainThread.isInterrupted());
        } catch (ExecutionException e) {
            System.out.println("ExecutionException " + e.getCause());
        } catch (TimeoutException e) {
            System.out.println("TimeoutException");
        } catch (CancellationException e) {
            System.out.println("CancellationException");
        } finally {
            executorService.shutdown();
        }
    }

看一下java.util.concurrent.FutureTask#get(long,TimeUnit)

java 复制代码
    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (unit == null)
            throw new NullPointerException();
        int s = state;
        if (s <= COMPLETING &&
            (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
            // 抛出超时异常
            throw new TimeoutException();
        return report(s);
    }

    private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            // 抛出取消异常,如果此时异步任务已经完成,是不会进入到这里,也就不会抛出异常
            throw new CancellationException();
        // 其余异常都被包装成ExecutionException
        throw new ExecutionException((Throwable)x);
    }

说完了FutureTask.get(long, TimeUnit)再看下CompletableFuture#get(long, TimeUnit)

java 复制代码
public T get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        long nanos = unit.toNanos(timeout);
        Object r;
        if ((r = result) == null)
        		 // 这里抛出TimeoutException
            r = timedGet(nanos);
        return (T) reportGet(r);
    }

	private static Object reportGet(Object r)
        throws InterruptedException, ExecutionException {
        if (r == null) 
            throw new InterruptedException();
        if (r instanceof AltResult) {
            Throwable x, cause;
            if ((x = ((AltResult)r).ex) == null)
                return null;
            if (x instanceof CancellationException)
                throw (CancellationException)x;
            // 注意这里有CompletionException,但是没有直接抛出,而是包装成ExecutionException再抛出
            if ((x instanceof CompletionException) &&
                (cause = x.getCause()) != null)
                x = cause;
            throw new ExecutionException(x);
        }
        return r;
    }

也举个例子

java 复制代码
public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Thread thread = Thread.currentThread();
        CompletableFuture<Object> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "sssss";
        }, executorService);

        // 中断主线程
        new Thread(() -> {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            thread.interrupt();
        }).start();

        try {
            Object s = future.get(1000, TimeUnit.MILLISECONDS);
        } catch (ExecutionException e) {
            System.out.println("ExecutionException");
        } catch (InterruptedException e) {
            System.out.println("InterruptedException");
        } catch (TimeoutException e) {
            System.out.println("TimeoutException");
        } finally {
            executorService.shutdown();
        }
    }

CompletableFuture.get(long,TimeUnit) VS CompletableFuture.join()

java 复制代码
	public T join() {
        Object r;
        if ((r = result) == null)
        		 // join不会超时,这里不会抛出超时异常
            r = waitingGet(false);
        return (T) reportJoin(r);
    }
    private static Object reportJoin(Object r) {
        if (r instanceof AltResult) {
            Throwable x;
            if ((x = ((AltResult)r).ex) == null)
                return null;
            // 这里还是会抛出CancellationException
            if (x instanceof CancellationException)
                throw (CancellationException)x;
            // 这里抛出的不再是ExecutionException,而是CompletionException
            if (x instanceof CompletionException)
                throw (CompletionException)x;
            // 其余异常包装成CompletionException
            throw new CompletionException(x);
        }
        return r;
    }

CompletableFuture.join()抛出异常分析

异常类型 是否受检 异常原因
InterruptedException join不会抛出InterruptedException
TimeoutException join不会抛出TimeoutException
CancellationException 任务完成前被cancel
ExecutionException join不会抛出ExecutionException
CompletionException 其他异常都会被包装成CompletionException,真实异常通过getCause获取

抛出CompletionException

java 复制代码
public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            throw new RuntimeException("wwwww");
        }, executorService);

        try {
            String s = future.join();
        } catch (CompletionException e) {
            System.out.println(e);
        } finally {
            executorService.shutdown();
        }
    }

抛出CancellationException

java 复制代码
public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(1);

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            return "";
        }, executorService);

        try {
            future.cancel(true);
            String s = future.join();
        } catch (CancellationException e) {
            System.out.println(e);
        } catch (CompletionException e) {
            System.out.println(e);
        } finally {
            executorService.shutdown();
        }
    }

CompletableFuture传播链及异常行为

传播链

可以把 CompletableFuture 理解成一条由多个"阶段(stage)"组成的流水线。每调用一次 thenApply、handle、exceptionally 等方法,通常都会创建并返回一个新的 CompletableFuture 对象。

java 复制代码
CompletableFuture<Integer> f1 =
        CompletableFuture.supplyAsync(() -> "123");

CompletableFuture<Integer> f2 =
        f1.thenApply(String::length);

CompletableFuture<String> f3 =
        f2.thenApply(length -> "长度:" + length);

每个节点都保存自己的完成状态:

  1. 未完成
  2. 正常完成
  3. 异常完成
  4. 被取消

上一个节点完成后,会触发依赖它的下一个节点。

返回的 Future 对象会变化吗

java 复制代码
public static void main(String[] args) {
        CompletableFuture<String> f1 =
                CompletableFuture.completedFuture("hello");

        CompletableFuture<Integer> f2 =
                f1.thenApply(String::length);

        CompletableFuture<String> f3 =
                f2.thenApply(String::valueOf);

        System.out.println(f1 == f3); // false
        // java: 不可比较的类型: java.util.concurrent.CompletableFuture<java.lang.Integer>和java.util.concurrent.CompletableFuture<java.lang.String>
        // System.out.println(f2 == f3);
    }

可以理解为:f1 ──依赖关系──> f2 ──依赖关系──> f3

写成链式调用时:

java 复制代码
CompletableFuture<String> future =
        CompletableFuture.supplyAsync(() -> "hello")
                .thenApply(String::length)
                .thenApply(String::valueOf);

变量 future 最终只保存最后一个阶段的引用。前面的对象依旧存在,只是没有被单独保存到变量中。

中间发生异常时如何传递

java 复制代码
CompletableFuture<String> future =
        CompletableFuture.supplyAsync(() -> "hello")
                .thenApply(value -> {
                    throw new IllegalStateException("转换失败");
                })
                .thenApply(value -> {
                    System.out.println("不会执行");
                    return value.toString();
                });

传递过程如下:

supplyAsync:正常完成,结果为 "hello"

第一个 thenApply:执行thenApply的Function ,返回一个异常结束状态的CompletableFuture,内部的异常被CompletionException包装

第二个 thenApply:跳过thenApply的Function ,返回一个异常结束状态的CompletableFuture,内部的异常被CompletionException包装

最终 future:就是第二个 thenApply返回的future,异常完成

当尝试

当尝试从future中获取结果时,不同的调用方法会得到不同的异常类型

java 复制代码
future.get();
ExecutionException
  └── IllegalStateException: 转换失败

future.join();
CompletionException
  └── IllegalStateException: 转换失败

区别主要是:

  • get() 抛出受检异常 ExecutionException
  • join() 抛出运行时异常 CompletionException
    正如我们上面提到的

三种常见异常处理方法

exceptionally:只处理异常

java 复制代码
CompletableFuture<String> future =
        CompletableFuture.supplyAsync(() -> {
            throw new RuntimeException("查询失败");
        }).exceptionally(throwable -> {
            System.out.println("发生异常:" + throwable.getMessage());
            // 使用 HTML span 标签使"默认值"在渲染时显示为红色(实际代码中字符串内容不变)
            return "默认值";
        });

上游异常

exceptionally 执行

返回 "默认值"

新的 future 正常完成

如果上游正常,exceptionally 不执行异常处理逻辑,正常结果直接传下去。

handle:正常和异常都会执行

java 复制代码
CompletableFuture<String> future =
        CompletableFuture.supplyAsync(() -> {
            throw new RuntimeException("失败");
        })
        .handle((result, error) -> error == null ? result : "fallback")
        .thenApply(value -> value + "-processed");

System.out.println(future.join());
上游状态 result throwable
正常完成 正常结果 null
异常完成 通常为 null 异常对象

whenComplete:观察结果,不负责转换

java 复制代码
CompletableFuture<String> future =
        CompletableFuture.supplyAsync(() -> {
            throw new RuntimeException("查询失败");
        }).whenComplete((result, throwable) -> {
            if (throwable != null) {
                System.out.println("记录异常");
            }
        });

取消在链中的传播

取消也是一种特殊的异常完成,但传播方向需要特别注意。

java 复制代码
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<String> f1 =
                CompletableFuture.supplyAsync(() -> {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return "ddd";
                });

        CompletableFuture<Integer> f2 =
                f1.thenApply(String::length);

        CompletableFuture<String> f3 =
                f2.thenApply(String::valueOf);
				// Exception in thread "main" java.util.concurrent.CancellationException
        f1.cancel(true);
        f1.join();
        // f2.join(); 抛出Exception in thread "main" java.util.concurrent.CompletionException: java.util.concurrent.CancellationException
    }

Future.cancel(boolean)是否结束任务线程

操作 FutureTask 中任务是否继续 CompletableFuture 中任务是否继续
get(timeout)超时 继续 继续
等待 get()的线程被中断 继续 继续
cancel(false) 已开始则通常继续,未开始则不执行 已开始通常继续,未开始也不保证阻止底层任务
cancel(true) 尝试中断工作线程,任务可能停止 不会中断工作线程,任务通常继续
orTimeout(...) 不适用 底层任务通常继续,只把 Future 标记为异常完成
completeOnTimeout(...) 不适用 底层任务通常继续,只给 Future 设置默认结果

可以看到只有cancel(true)有可能终止任务线程,而且需要任务线程响应中断,阻塞方法天然能通过中断事件响应中断

java 复制代码
public static void main(String[] args)  {
        ExecutorService executorService = Executors.newFixedThreadPool(1);

        Thread mainThread = Thread.currentThread();

        Future<String> future = executorService.submit(() -> {
            Thread.sleep(1000); // 1
            return "ok";
        });

        // 中断执行get的线程
        new Thread(() -> {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            mainThread.interrupt();  // 2
        }).start();

        try {
            future.get(1000, TimeUnit.MILLISECONDS); // 3
        } catch (InterruptedException e) {
            System.out.println("InterruptedException mainThread before " + mainThread.isInterrupted());
            // InterruptedException会清除中断标记,生产代码通常需要恢复
            Thread.currentThread().interrupt();
            System.out.println("InterruptedException mainThread after " + mainThread.isInterrupted());
        } catch (ExecutionException e) {
            System.out.println("ExecutionException " + e.getCause());
        } catch (TimeoutException e) {
            System.out.println("TimeoutException");
        } catch (CancellationException e) {
            System.out.println("CancellationException");
        } finally {
            executorService.shutdown();
        }
    }

执行步骤:

1、 先执行future.get(1000, TimeUnit.MILLISECONDS); 注释3

2、 再执行Thread.sleep(1000);注释1

3、 再执行mainThread.interrupt();注释2。此时mainThread会在future.get(1000, TimeUnit.MILLISECONDS);被唤醒,并抛出InterruptedException

相关推荐
林森lsjs2 小时前
队列 FIFO 原理 & 手撕两种队列实现(链表 + 循环数组)—数据结构陆
java·开发语言·数据结构·队列
lisin-lee-cooper3 小时前
简单聊聊 OpenFeign 框架源码
java·微服务
Buke..3 小时前
【APP 逆向】哔哩哔哩 sign 参数逆向(下):OLLVM 混淆还原 sign 算法
java·javascript·爬虫·python·算法
linux-hzh3 小时前
百日算法修炼 · Day 15
java·算法
半生过往3 小时前
前端学 Java 课程笔记
java·前端·笔记
mmsx4 小时前
基于 Android 的校园信息管理系统源码
android·java·okhttp
Buke..4 小时前
【APP 逆向】哔哩哔哩 sign 参数逆向(上):Frida 反调试绕过与 unidbg 调用
java·开发语言·爬虫·python·安卓
竹枝溪4 小时前
Tomcat与Servlet全套小白教程
java·servlet·tomcat·cookie·filter·session·listener
UseLessQQ5 小时前
云计算k8s集群部署
java·kubernetes·云计算