并发编程从入门到放弃系列开始和结束(七)

wg204wg
发布于 2022-6-13 17:40
浏览
0收藏

 

public static void main(String[] args) throws Exception {
   //        testInvokeAny();
   testCompletionService();
}
private static void testCompletionService() {
    CompletionService<String> completionService = new ExecutorCompletionService(executorService);
    List<Future> taskList = new LinkedList<>();
    for (int i = 0; i < TOTAL; i++) {
        taskList.add(completionService.submit(new TestTask(i)));
    }

    String value = null;
    try {
        for (int i = 0; i < TOTAL; i++) {
            value = completionService.take().get();
            if (value != null) {
                System.out.println("get value = " + value);
                break;
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        taskList.forEach(task -> {
            task.cancel(true);
        });
    }

    executorService.shutdown();
}
//输出
task-4 sleep 5006 Ms
task-1 sleep 4114 Ms
task-2 sleep 4865 Ms
task-5 sleep 1592 Ms
task-3 sleep 6190 Ms
task-7 sleep 2482 Ms
task-8 sleep 9405 Ms
task-9 sleep 8798 Ms
task-6 sleep 2040 Ms
task-0 sleep 2111 Ms
get value = task-5

效果是一样的,我们只是实现了一个简化版的 invokeAny 功能,使用起来也挺简单的。

实现原理也挺简单的,哪个任务先完成,就把他丢到阻塞队列里,这样取任务结果的时候直接从队列里拿,肯定是拿到最新的那一个。

异步结果
通常,我们都会用 FutureTask 来获取线程异步执行的结果,基于 AQS 实现。

 并发编程从入门到放弃系列开始和结束(七)-鸿蒙开发者社区
这个没有说太多的必要,看看几个方法就行了。

public V get();
public V get(long timeout, TimeUnit unit);
public boolean cancel(boolean mayInterruptIfRunning);

get 会阻塞的获取线程异步执行的结果,一般不建议直接使用,最好是使用带超时时间的 get 方法。

我们可以通过 cancel 方法去尝试取消任务的执行,参数代表是否支持中断,如果任务未执行,那么可以直接取消,如果任务执行中,使用 cancel(true) 会尝试中断任务。

CompletableFuture
之前我们都在使用 Future,要么只能用 get 方法阻塞,要么就用 isDone 来判断,JDK1.8 之后新增了 CompletableFuture 用于异步编程,它针对 Future 的功能增加了回调能力,可以帮助我们简化异步编程。

CompletableFuture 主要包含四个静态方法去创建对象,主要区别在于 supplyAsync 返回计算结果,runAsync 不返回,另外两个方法则是可以指定线程池,如果不指定线程池则默认使用 ForkJoinPool,默认线程数为CPU核数。

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
public static CompletableFuture<Void> runAsync(Runnable runnable);
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);

下面看看他的那些恶心人的几十个方法,我估计能疯。

串行

串行就不用解释了,A->B->C 按照顺序执行,下一个任务必须等上一个任务执行完成才可以。

主要包含 thenApply、thenAccept、thenRun 和 thenCompose,以及他们对应的带 async 的异步方法。

为了方便记忆我们要记住,有 apply 的有传参有返回值,带 accept 的有传参但是没有返回值,带 run 的啥也没有,带 compose 的会返回一个新的 CompletableFuture 实例。

public static void main(String[] args) throws Exception {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread() + "工作完成");
            return "supplyAsync";
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    });
    CompletableFuture newFuture = future.thenApply((ret) -> {
        System.out.println(Thread.currentThread() + "thenApply=>" + ret);
        return "thenApply";
    }).thenAccept((ret) -> {
        System.out.println(Thread.currentThread() + "thenAccept=>" + ret);
    }).thenRun(() -> {
        System.out.println(Thread.currentThread() + "thenRun");
    });
    CompletableFuture<String> composeFuture = future.thenCompose((ret) -> {
        System.out.println(Thread.currentThread() + "thenCompose=>" + ret);
        return CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread() + "thenCompose工作完成");
                return "thenCompose";
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
    });

    System.out.println(future.get());
    System.out.println(newFuture.get());
    System.out.println(composeFuture.get());
}
//输出
Thread[ForkJoinPool.commonPool-worker-9,5,main]工作完成
Thread[ForkJoinPool.commonPool-worker-9,5,main]thenCompose=>supplyAsync
Thread[main,5,main]thenApply=>supplyAsync
Thread[main,5,main]thenAccept=>thenApply
Thread[main,5,main]thenRun
supplyAsync
null
Thread[ForkJoinPool.commonPool-worker-2,5,main]thenCompose工作完成
thenCompose

AND 聚合

这个意思是下一个任务执行必须等前两个任务完成可以。

主要包含 thenCombine、thenAcceptBoth、runAfterBoth ,以及他们对应的带 async 的异步方法,区别和上面一样。

public static void main(String[] args) throws Exception {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread() + "A工作完成");
            return "A";
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    });

    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(2000);
            System.out.println(Thread.currentThread() + "B工作完成");
            return "B";
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    });

    CompletableFuture newFuture = future.thenCombine(future2, (ret1, ret2) -> {
        System.out.println(Thread.currentThread() + "thenCombine=>" + ret1 + "," + ret2);
        return "thenCombine";
    }).thenAcceptBoth(future2, (ret1, ret2) -> {
        System.out.println(Thread.currentThread() + "thenAcceptBoth=>" + ret1 + "," + ret2);
    }).runAfterBoth(future2, () -> {
        System.out.println(Thread.currentThread() + "runAfterBoth");
    });

    System.out.println(future.get());
    System.out.println(future2.get());
    System.out.println(newFuture.get());
}
//输出
Thread[ForkJoinPool.commonPool-worker-9,5,main]A工作完成
A
Thread[ForkJoinPool.commonPool-worker-2,5,main]B工作完成
B
Thread[ForkJoinPool.commonPool-worker-2,5,main]thenCombine=>A,B
Thread[ForkJoinPool.commonPool-worker-2,5,main]thenAcceptBoth=>thenCombine,B
Thread[ForkJoinPool.commonPool-worker-2,5,main]runAfterBoth
null

Or 聚合

Or 聚合代表只要多个任务中有一个完成了,就可以继续下面的任务。

主要包含 applyToEither、acceptEither、runAfterEither ,以及他们对应的带 async 的异步方法,区别和上面一样,不再举例了。

回调/异常处理

whenComplete、handle 代表执行完成的回调,一定会执行,exceptionally 则是任务执行发生异常的回调。

public static void main(String[] args) throws Exception {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(1000);
            int a = 1 / 0;
            return "success";
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    });

    CompletableFuture newFuture = future.handle((ret, exception) -> {
        System.out.println(Thread.currentThread() + "handle exception=>" + exception.getMessage());
        return "handle";
    });

    future.whenComplete((ret, exception) -> {
        System.out.println(Thread.currentThread() + "whenComplete exception=>" + exception.getMessage());
    });

    CompletableFuture exceptionFuture = future.exceptionally((e) -> {
        System.out.println(Thread.currentThread() + "exceptionally exception=>" + e.getMessage());
        return "exception";
    });

    System.out.println("task future = " + future.get());
    System.out.println("handle future = " + newFuture.get());
    System.out.println("exception future = " + exceptionFuture.get());
}
//输出
Thread[ForkJoinPool.commonPool-worker-9,5,main]exceptionally exception=>java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
Thread[main,5,main]whenComplete exception=>java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
Thread[ForkJoinPool.commonPool-worker-9,5,main]handle exception=>java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
 at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
 at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1908)
 at com.example.demo.CompletableFutureTest3.main(CompletableFutureTest3.java:31)
Caused by: java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
 at com.example.demo.CompletableFutureTest3.lambda$main$0(CompletableFutureTest3.java:13)
 at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1604)
 at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1596)
 at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
 at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1067)
 at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1703)
 at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:172)
Caused by: java.lang.ArithmeticException: / by zero
 at com.example.demo.CompletableFutureTest3.lambda$main$0(CompletableFutureTest3.java:10)
 ... 6 more

 

文章转自公众号:艾小仙

分类
标签
已于2022-6-13 17:40:44修改
收藏
回复
举报
回复
    相关推荐