Java8异步编程

Java8异步类CompletableFuture详解 JDK8引入了一个新的类CompletableFuture 阅读详情

一、引言

参考Java多线程几种实现方式,我们可以很快速的通过new Thread(...).start()开启一个新的线程,但是这样创建线程会有很多坏处:

  • 每次都要新建一个对象,性能差;
  • 建出来的很多个对象是独立的,缺乏统一的管理。如果在代码中无限新建线程会导致这些线程相互竞争,占用过多的系统资源从而导致死机或者 oom;
  • 缺乏许多功能如定时执行、中断等。

因此Java给我们提供好一个十分好用的工具,那就是线程池。线程池(ThreadPool)是一种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在一个"池子"内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从"池子"内取出相应的线程执行对应的任务即可。

二、Java Executors线程池

1、Java 线程池概述

Java提供了一个工厂类来构造我们需要的线程池,这个工厂类就是 Executors 。这里主要讲6个创建线程池的方法。默认拒绝策略都为AbortPolicy,即丢弃任务并抛出RejectedExecutionException异常

  • newCachedThreadPool()
  • newFixedThreadPool(int nThreads)
  • newScheduledThreadPool(int corePoolSize)
  • newSingleThreadExecutor()
  • newSingleThreadScheduledExecutor()
  • newWorkStealingPool()

2、newCachedThreadPool()

创建缓存线程池。缓存的意思就是这个线程池会根据需要创建新的线程,在有新任务的时候会优先使用先前创建出的线程。线程一旦创建了就一直在这个池子里面了,执行完任务后后续还有任务需要会重用这个线程,若是线程不够用了再去新建线程。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
  • corePoolSize = 0
  • maximumPoolSize = Integer.MAX_VALUE,即线程数量几乎无限制
  • keepAliveTime = 60s,线程空闲 60s 后自动结束
  • workQueue 为 SynchronousQueue 同步队列,这个队列类似于一个接力棒,入队出队必须同时传递,因为 CachedThreadPool 线程创建无限制,不会有队列等待,所以使用 SynchronousQueue

适用场景:快速处理大量耗时较短的任务,如 Netty 的 NIO 接受请求时,可使用 CachedThreadPool

ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
    final int index = i;

    // 每次发布任务前根据奇偶不同等待一段时间,如1s,这样就会创建两个线程
    if (i % 2 == 0) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 执行任务
    executorService.execute(() -> System.out.println(Thread.currentThread().getName() + ":" + index));
}

但注意这里的线程池是无限大的,并没有规定他的大小

3、newFixedThreadPool(int nThreads)

创建定长线程池,参数是线程池的大小。也就是说,在同一时间执行的线程数量只能是 nThreads 这么多,这个线程池可以有效的控制最大并发数从而防止占用过多资源。超出的线程会放在线程池的一个无界队列里等待其他线程执行完。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

  • corePoolSize 与 maximumPoolSize 相等,即其线程全为核心线程,是一个固定大小的线程池,是其优势;
  • keepAliveTime = 0 该参数默认对核心线程无效,而 FixedThreadPool 全部为核心线程;
  • workQueue 为 LinkedBlockingQueue(无界阻塞队列),队列最大值为 Integer.MAX_VALUE。如果任务提交速度持续大于任务处理速度,会造成队列大量阻塞。因为队列很大,很有可能在拒绝策略前,内存溢出。是其劣势;
  • FixedThreadPool 的任务执行是无序的;

适用场景:可用于 Web 服务瞬时削峰,但需注意长时间持续高峰情况造成的队列阻塞。

4、newScheduledThreadPool(int corePoolSize)

ScheduledThreadPool创建一个可以执行延迟任务的线程池,同时这个线程池也是定长的,参数 corePoolSize 就是线程池的大小,即在空闲状态下要保留在池中的线程数量。而要实现调度需要使用这个线程池的 schedule() 方法,队列使用了DelayedWorkQueue延迟队列

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
// 三秒后执行
scheduledExecutorService.schedule(() -> System.out.println(Thread.currentThread().getName() + ": 我会在3秒后执行。"),
                3, TimeUnit.SECONDS);

5、newSingleThreadExecutor()

创建单线程池,只使用一个线程来执行任务。但是它与 newFixedThreadPool(1, threadFactory) 不同,它会保证创建的这个线程池不会被重新配置为使用其他的线程,也就是说这个线程池里的线程始终如一,同时可以保证先进先出的执行顺序

ExecutorService executorService = Executors.newSingleThreadExecutor();

6、newSingleThreadScheduledExecutor()

创建一个单线程的可以执行延迟任务的线程池

ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
System.out.println("添加任务,时间:" + new Date());
threadPool.schedule(() -> {
    System.out.println("任务被执行,时间:" + new Date());
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    }
}, 2, TimeUnit.SECONDS);

7、newWorkStealingPool()

创建一个抢占式执行的线程池(任务执行顺序不确定),注意此方法只有在 JDK 1.8+ 版本中才能使用

ExecutorService threadPool = Executors.newWorkStealingPool();
for (int i = 0; i < 10; i++) {
    final int index = i;
    threadPool.execute(() -> {
        System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
    });
}
// 确保任务执行完成
while (!threadPool.isTerminated()) {
}

8、线程池的关闭

线程池启动后需要手动关闭,否则会一直不结束

  • shutdown() : 将线程池状态置成 SHUTDOWN,此时不再接受新的任务,等待线程池中已有任务执行完成后结束;
  • shutdownNow() : 将线程池状态置成 SHUTDOWN,将线程池中所有线程中断(调用线程的 interrupt() 操作),清空队列,并返回正在等待执行的任务列表。

并且它还提供了查看线程池是否关闭和是否终止的方法,分别为 isShutdown() 和 isTerminated() 。

三、自定义线程池ThreadPoolExecutor

1、概述

阿里巴巴的JAVA开发手册推荐用ThreadPoolExecutor创建线程池,可以规避资源耗尽的风险。因为Executors线程池都不支持自定义拒绝策略。newFixedThreadPool 和 newSingleThreadExecutor主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。newCachedThreadPool 和 newScheduledThreadPool主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。

2、自定义线程池参数介绍

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler);
  • corePoolSize:核心线程数,线程池中始终存活的线程数
  • maximumPoolSize:最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数
  • keepAliveTime:最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程
  • unit:keepAliveTime的时间单位,和keepAliveTime配合使用
  • workQueue:一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全。它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种
    • SynchronousQueue(常用):直接提交队列。SynchronousQueue没有容量,所以实际上提交的任务不会被添加到任务队列,总是将新任务提交给线程执行,如果没有空闲的线程,则尝试创建新的线程,如果线程数量已经达到最大值(maximumPoolSize),则执行拒绝策略
    • LinkedBlockingQueue(常用):无界的任务队列。当有新的任务来到时,若系统的线程数小于corePoolSize,线程池会创建新的线程执行任务;当系统的线程数量等于corePoolSize后,因为是无界的任务队列,总是能成功将任务添加到任务队列中,所以线程数量不再增加。若任务创建的速度远大于任务处理的速度,无界队列会快速增长,直到内存耗尽。
    • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列
    • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列
    • DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素
    • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法
    • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列
  • threadFactory:线程工厂,用于创建线程,一般情况下使用默认的,即Executors类的静态方法defaultThreadFactory();
  • handler:拒绝策略。当任务太多来不及处理时,如何拒绝任务。系统提供四种,默认策略为 AbortPolicy
    • DiscardOldestPolicy:丢弃任务队列中最早添加的任务,并尝试提交当前任务
    • CallerRunsPolicy:调用主线程执行被拒绝的任务,这提供了一种简单的反馈控制机制,将降低新任务的提交速度
    • DiscardPolicy:默默丢弃无法处理的任务,不予任何处理
    • AbortPolicy:直接抛出异常,阻止系统正常工作

线程池的执行流程

  • 当线程数小于核心线程数时,创建线程
  • 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列
  • 当线程数大于等于核心线程数,且任务队列已满:若线程数小于最大线程数,创建线程;若线程数等于最大线程数,抛出异常,拒绝任务

3、自定义线程池demo

@Slf4j
public class ThreadPoolService {

    public static void main(String[] args) throws Exception {


        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
        NameThreadFactory threadFactory = new NameThreadFactory();
        RejectedExecutionHandler handler = new MyIgnorePolicy();
        //默认线程工程 Executors.defaultThreadFactory()
        //默认拒绝策略 new ThreadPoolExecutor.AbortPolicy()
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 2000, TimeUnit.MILLISECONDS,
                workQueue, threadFactory,  handler);
        // 预启动所有核心线程
        executor.prestartAllCoreThreads();

        for (int i = 1; i <= 10; i++) {
            MyTask task = new MyTask(String.valueOf(i));
            executor.execute(task);
        }

        //阻塞主线程
        System.in.read();
    }

    /**
     * Description:自定义线程名字
    */
    static class NameThreadFactory implements ThreadFactory {
        private final AtomicInteger mThreadNum = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "my-thread-" + mThreadNum.getAndIncrement());
            System.out.println(t.getName() + " has been created");
            return t;
        }
    }

    static class MyIgnorePolicy implements RejectedExecutionHandler {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            doLog(r, executor);
        }

        private void doLog(Runnable r, ThreadPoolExecutor e) {
            // 可做日志记录等
            System.err.println( r.toString() + " rejected");
            System.out.println("completedTaskCount: " + e.getCompletedTaskCount());
        }
    }

    static class MyTask implements Runnable {
        private String name;

        public MyTask(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            try {
                System.out.println(this.toString() + " is running!");
                // 让任务执行慢点
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public String getName() {
            return name;
        }

        @Override
        public String toString() {
            return "MyTask [name=" + name + "]";
        }
    }
}


四、异步编程函数式接口

1、简介

函数式接口可以参考java8常用新特性,这里主要介绍几种函数式接口,Callable、Runnable、Future、CompletableFuture和FutureTask

2、Callable和Runnable异同

//两个接口的定义
@FunctionalInterface
public interface Runnable {
    
    public abstract void run();
}

@FunctionalInterface
public interface Callable<V> {
    
    V call() throws Exception;
}

相同点

都是接口,都可以编写多线程程序,都可以通过线程池启动线程

不同点

Runnable没有返回值,Callable可以返回执行结果,是个泛型;Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

public class Test1 {

    static class Min implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            Thread.sleep(1000);
            return 4;
        }
    }
    static class Max implements Runnable{
        @SneakyThrows
        @Override
        public void run() {
            Thread.sleep(1000);
        }
    }
    
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(new Min());
        executorService.submit(new Max());
    }

3、Future类

3.1 future介绍

Future是Java5新加的一个接口,它提供了一种异步并行计算的功能。如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中执行。主线程继续处理其他任务,处理完成后,再通过Future获取计算结果

//Since:1.5
public interface Future<V> {
    //取消任务的执行,参数指定是否立即中断任务执行,或者等等任务结束
    boolean cancel(boolean mayInterruptIfRunning);
    //任务是否已经取消,任务正常完成前将其取消,则返回 true
    boolean isCancelled();
    //任务是否已经完成。需要注意的是如果任务正常终止、异常或取消,都将返回true
    boolean isDone();
    //等待任务执行结束,然后获得V类型的结果。InterruptedException 线程被中断异常, ExecutionException任务执行异常,如果任务被取消,还会抛出CancellationException
    V get() throws InterruptedException, ExecutionException;
    //同上面的get功能一样,多了设置超时时间。超时会抛出TimeoutException
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

一般情况下,我们会结合Callable和Future一起使用,通过ExecutorService的submit方法执行Callable,并返回Future。

 public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        ExecutorService executor = Executors.newCachedThreadPool();
        //Lambda 是一个 callable, 提交后便立即执行,这里返回的是 FutureTask 实例
        Future<String> future = executor.submit(() -> {
            System.out.println("running task");
            Thread.sleep(10000);
            return "return task";
        });
        future.get(2, TimeUnit.SECONDS);
    }

当然Future模式也有它的缺点,它没有提供通知的机制,我们无法得知Future什么时候完成。如果要在future.get()的地方等待future返回的结果,那只能通过isDone()轮询查询。

3.2 FutureTask

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/FutureTask.html

Future是一个接口,是无法生成一个实例的,所以又有了FutureTask。FutureTask实现了RunnableFuture接口,RunnableFuture接口又实现了Runnable接口和Future接口。所以FutureTask既可以被当做Runnable来执行,也可以被当做Future来获取Callable的返回结果。

4、CompletableFuture类

4.1 介绍

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html

CompletableFuture能够将回调放到与任务不同的线程中执行,也能将回调作为继续执行的同步函数,在与任务相同的线程中执行。它避免了传统回调最大的问题,那就是能够将控制流分离到不同的事件处理器中。CompletableFuture弥补了Future模式的缺点。在异步的任务完成后,需要用其结果继续操作时,无需等待。可以直接通过thenAccept、thenApply、thenCompose等方式将前面异步处理的结果交给另外一个异步事件处理线程来处理。

4.2 异步任务创建

CompletableFuture创建异步任务,一般有supplyAsync和runAsync两个方法

  • supplyAsync执行CompletableFuture任务,支持返回值
  • runAsync执行CompletableFuture任务,没有返回值
方法名描述
runAsync(Runnable runnable)使用ForkJoinPool.commonPool()作为它的线程池执行异步代码
runAsync(Runnable runnable, Executor executor)使用指定的thread pool执行异步代码
supplyAsync(Supplier supplier)使用ForkJoinPool.commonPool()作为它的线程池执行异步代码,异步操作有返回值
supplyAsync(Supplier supplier, Executor executor)使用指定的thread pool执行异步代码,异步操作有返回值
public static void main(String[] args) {
    //可以自定义线程池
    ExecutorService executor = Executors.newCachedThreadPool();
    //runAsync的使用
    CompletableFuture<Void> runFuture = CompletableFuture.runAsync(() -> System.out.println("run,shawn"), executor);
    //supplyAsync的使用
    CompletableFuture<String> supplyFuture = CompletableFuture.supplyAsync(() -> {
                System.out.print("supply,shawn");
                return "shawn"; }, executor);
    //runAsync的future没有返回值,输出null
    System.out.println(runFuture.join());
    //supplyAsync的future,有返回值
    System.out.println(supplyFuture.join());
    executor.shutdown(); // 线程池需要关闭
}

4.3 任务异步回调

在这里插入图片描述

  • thenRun/thenRunAsync

    做完第一个任务后,再做第二个任务。某个任务执行完成后,执行回调方法;但是前后两个任务没有参数传递,第二个任务也没有返回值

  • thenAccept/thenAcceptAsync

    第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,但是回调方法是没有返回值的。

  • thenApply/thenApplyAsync

    第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,并且回调方法是有返回值的。

注意:上述三个方法如果执行第一个任务的时候,传入了一个自定义线程池:

  • 调用xxx方法执行第二个任务时,则第二个任务和第一个任务是共用同一个线程池。
  • 调用xxxAsync执行第二个任务时,则第一个任务使用的是你自己传入的线程池,第二个任务使用的是ForkJoin线程池
//举个例子
public static void main(String[] args) throws Exception {
        CompletableFuture<String> orgFuture = CompletableFuture.supplyAsync(
                ()->{
                    System.out.println("原始CompletableFuture方法任务");
                    return "shawn";
                }
        );
        CompletableFuture<String> thenApplyFuture = orgFuture.thenApply((a) -> {
            if ("shawn".equals(a)) {
                return "确实很帅";
            }
            return "考虑考虑";
        });

        System.out.println(thenApplyFuture.get());
    }
  • exceptionally

    某个任务执行异常时,执行的回调方法;并且有抛出异常作为参数,传递到回调方法

  • whenComplete

    某个任务执行完成后,执行的回调方法,无返回值;并且whenComplete方法返回的CompletableFuture的result是上个任务的结果

  • handle

    某个任务执行完成后,执行回调方法,并且是有返回值的;并且handle方法返回的CompletableFuture的result是回调方法执行的结果

//举例
CompletableFuture<String> orgFuture = CompletableFuture.supplyAsync(
            ()->{
                System.out.println("当前线程名称:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "shawn";
            }
    );
    //exceptionally使用方法是orgFuture.exceptionally((e)->{...})
    CompletableFuture<String> rstFuture = orgFuture.handle((a, throwable) -> {

        System.out.println("上个任务执行完啦,还把" + a + "传过来");
        if ("shawn".equals(a)) {
            System.out.println("666");
            //whenComplete没有返回
            return "好帅啊";
        }
        System.out.println("233333");
        return null;
    });

    System.out.println(rstFuture.get());
}

最后对于complete方法

public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
        //在这里执行返回值为World
        // future.complete("World");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //在这里执行结果为Hello
        future.complete("World");

        try {
            //get() 方法会抛出经检查的异常,可被捕获,自定义处理或者直接抛出。join() 会抛出未经检查的异常。
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

4.4 多个任务组合处理

  • AND组合关系

    thenCombine / thenAcceptBoth / runAfterBoth都表示:将两个CompletableFuture组合起来,只有这两个都正常执行完了,才会执行某个任务。

    区别在于:

    • thenCombine:会将两个任务的执行结果作为方法入参,传递到指定方法中,且有返回值
    • thenAcceptBoth: 会将两个任务的执行结果作为方法入参,传递到指定方法中,且无返回值
    • runAfterBoth 不会把执行结果当做方法入参,且没有返回值。
  • OR组合关系

    applyToEither / acceptEither / runAfterEither 都表示:将两个CompletableFuture组合起来,只要其中一个执行完了,就会执行某个任务。

    区别在于:

    • applyToEither:会将已经执行完成的任务,作为方法入参,传递到指定方法中,且有返回值
    • acceptEither: 会将已经执行完成的任务,作为方法入参,传递到指定方法中,且无返回值
    • runAfterEither:不会把执行结果当做方法入参,且没有返回值。
//举个例子
public static void main(String[] args) {
    //第一个异步任务,休眠2秒,保证它执行晚点
    CompletableFuture<String> first = CompletableFuture.supplyAsync(()->{
        try{

            Thread.sleep(2000L);
            System.out.println("执行完第一个异步任务");}
        catch (Exception e){
            return "第一个任务异常";
        }
        return "第一个异步任务";
    });
    ExecutorService executor = Executors.newSingleThreadExecutor();
    CompletableFuture<Void> future = CompletableFuture
            //第二个异步任务
            .supplyAsync(() -> {
                        System.out.println("执行完第二个任务");
                        return "第二个任务";}
                    , executor)
            //第三个任务
            .acceptEitherAsync(first, System.out::println, executor);

    executor.shutdown();
}

  • AllOf

    所有任务都执行完成后,才执行 allOf返回的CompletableFuture。如果任意一个任务异常,allOf的CompletableFuture,执行get方法,会抛出异常

  • AnyOf

    任意一个任务执行完,就执行anyOf返回的CompletableFuture。如果执行的任务异常,anyOf的CompletableFuture,执行get方法,会抛出异常

//举例
public static void main(String[] args) {

    CompletableFuture<Void> a = CompletableFuture.runAsync(()->{
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我执行完了");
    });
    CompletableFuture<Void> b = CompletableFuture.runAsync(() -> {
        System.out.println("我也执行完了");
    });
    CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(a, b).whenComplete((m,k)->{
        System.out.println("finish");
        //handle可以返回
//            return "shawn";
    });
    anyOfFuture.join();
}

  • thenCompose

    thenCompose方法会在某个任务执行完成后,将该任务的执行结果,作为方法入参,去执行指定的方法。该方法会返回一个新的CompletableFuture实例

    • 如果该CompletableFuture实例的result不为null,则返回一个基于该result新的CompletableFuture实例;
    • 如果该CompletableFuture实例为null,然后就执行这个新任务
//举个例子
public static void main(String[] args) throws ExecutionException, InterruptedException {

    CompletableFuture<String> f = CompletableFuture.completedFuture("第一个任务");
    //第二个异步任务
    ExecutorService executor = Executors.newSingleThreadExecutor();
    CompletableFuture<String> future = CompletableFuture
            .supplyAsync(() -> "第二个任务", executor)
            .thenComposeAsync(data -> {
                System.out.println(data); return f; //使用第一个任务作为返回
            }, executor);
    //get() 方法会抛出经检查的异常,可被捕获,自定义处理或者直接抛出。而 join() 会抛出未经检查的异常。
    System.out.println(future.join());
    executor.shutdown();

}

4.5 CompletableFuture使用注意点

  • Future需要获取返回值,才能获取异常信息

    Future需要获取返回值,才能获取到异常信息。如果不加 get()/join()方法,看不到异常信息。小伙伴们使用的时候,注意一下哈,考虑是否加try...catch...或者使用exceptionally方法

  • CompletableFuture的get()方法是阻塞的

    CompletableFuture的get()方法是阻塞的,如果使用它来获取异步调用的返回值,需要添加超时时间

  • 默认线程池的注意点

    CompletableFuture代码中又使用了默认的线程池,处理的线程个数是电脑CPU核数-1。在大量请求过来的时候,处理逻辑复杂的话,响应会很慢。一般建议使用自定义线程池,优化线程池配置参数。

  • 自定义线程池时,注意饱和策略

    如果线程池拒绝策略是DiscardPolicy或者DiscardOldestPolicy,当线程池饱和时,会直接丢弃任务,不会抛弃异常。因此建议CompletableFuture线程池策略最好使用AbortPolicy,然后耗时的异步线程,做好线程池隔离

5、CompletionService使用

5.1 介绍

和CompletableFuture类似,CompletionService是对定义ExecutorService进行了包装,可以一边生成任务,一边获取任务的返回值。让这两件事分开执行,任务之间不会互相阻塞,可以获取最先完成的任务结果。

CompletionService的实现原理比较简单,底层通过FutureTask+阻塞队列,实现了任务先完成的话,可优先获取到。也就是说任务执行结果按照完成的先后顺序来排序,先完成可以优化获取到。内部有一个先进先出的阻塞队列,用于保存已经执行完成的Future,你调用CompletionService的poll或take方法即可获取到一个已经执行完成的Future,进而通过调用Future接口实现类的get方法获取最终的结果

5.2 demo代码

ExecutorService executor = Executors.newFixedThreadPool(10);
//查询用户信息
CompletionService<Object> baseDTOCompletionService = new ExecutorCompletionService<Object>(executor);
Callable<Object> userInfoDTOCallableTask = () -> {
    UserInfoParam userInfoParam = buildUserParam(req);
    return userService.queryUserInfo(userInfoParam);
};
//banner信息任务
Callable<Object> bannerDTOCallableTask = () -> {
    BannerParam bannerParam = buildBannerParam(req);
    return bannerService.queryBannerInfo(bannerParam);
};

//提交用户信息任务
baseDTOCompletionService.submit(userInfoDTOCallableTask);
//提交banner信息任务
baseDTOCompletionService.submit(bannerDTOCallableTask);

//Future<Object> baseRspDTOFuture = baseDTOCompletionService.poll(1, TimeUnit.SECONDS);

参考文章

https://juejin.cn/post/6970607007669682212

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Callable.html#call

https://www.jianshu.com/p/dff9063e1ab6

CompletableFuture详解

线程池的 7 种创建方式及自定义线程池

异步编程利器:CompletableFuture详解 |Java 开发实战 06日阅读 1005 关注 前言 文正在参加「Java主题月 - Java 开发实战」, 我们异步执行一个任务时,一般是用线程池Executor去创建。如果不需要有返回值, 任务实现Runnable接口;如果需要有返回值,任务实现Callable接口,调用Executor的submit方法,再使用Future获取即可。如果多个线程存在依赖组合的话,我们怎么处理呢?可使用同步组件CountDownLatch、CyclicBarrier等,但是比较麻烦。其实有简单的方法,就是用Compele.. 阅读详情

相关推荐

Java8 异步非阻塞做法:CompletableFuture 两万字详解

CompletableFuture实现了CompletionStage接口和Future接口,前者是对后者的一个扩展,增加了异步回调、流式处理、多个Future组合处理的能力,使Java在处理多任务的协同工作时更加顺畅便利。一、创建异步任务1、Future.submit通常的线程池接口类ExecutorService,其中execute方法的返回值是void,即无法获取异步任务的执行状态,3个重载的submit方法的返回值是Future,可以据此获取任务执行的状态和结果,示例如下: @Test

代码界的扛把子 243

【Java8新特性--->异步处理】CompletableFuture

【Java1.8新特性--->异步处理】ComplatableFuture

qq_52793248的博客 5132

CompletableFuture:Java 8 中的异步编程利器

CompletableFuture 是 Java 8 引入的 java.util.concurrent 包下的一个类,它代表一个异步计算的结果,可以是已完成、正在进行或尚未开始。CompletableFuture 提供了一种灵活、类型安全的方式来表达异步操作的生命周期,包括创建、组合、处理结果以及处理异常。其设计灵感来源于函数式编程中的 Promises/Futures 模式,旨在简化异步编程模型,提高代码的可读性和可维护性。

wjianwei666的专栏 1120

Java 的 8 种异步实现方式,让性能飞起来

异步执行对于开发者来说并不陌生,在实际的开发过程中,很多场景多会使用到异步,相比同步执行,异步可以大大缩短请求链路耗时时间,比如:发送短信、邮件、异步更新等,这些都是典型的可以通过异步实现的场景。异步的八种实现方式线程ThreadFuture异步框架CompletableFutureSpring注解@AsyncSpring ApplicationEvent事件消息队列第三方异步框架,比如Hutoo...

天行健,君子以自强不息;地势坤,君子以厚德载物。 7238

告别电压瓶颈:手把手教你用SVPWM过调制算法提升电机驱动性能(附Simulink仿真)

本文详细介绍了如何利用SVPWM过调制算法提升电机驱动性能,解决电动汽车高速运行时的电压瓶颈问题。通过过调制I区和II区的算法实现细节,包括相角补偿与跳变控制,结合Simulink仿真演示,帮助工程师和研究者平衡电压增益与谐波失真,提升系统效率与动态响应。

weixin_30315723的博客 392

Ubuntu22.04 上ROS1 Noetic的安装与常见问题解决

本文详细介绍了在Ubuntu 22.04系统上安装ROS1 Noetic的两种主要方法:推荐使用社区维护的适配仓库进行便捷安装,并提供了从源码编译的备选方案。文章重点解决了安装后编译ROS包时极易遇到的“shared_mutex”等C++标准库冲突问题,并给出了其他常见错误的排查与解决指南,帮助开发者顺利搭建稳定的ROS开发环境。

weixin_29295541的博客 364

java8 CompletableFuture异步编程

它们提供了一种执行异步任务的机制,可以管理线程池、调度任务的执行和获取任务的结果。CompletableFuture:CompletableFuture 类是 Java 8 引入的新类,用于支持异步编程和处理异步任务的结果。通过使用 CompletableFuture 和 CompletionStage,我们可以实现灵活且高效的异步编排,实现串行、并行和组合等复杂的异步操作。通过 CompletableFuture 的链式调用和组合,我们可以更灵活地编排和处理异步任务,实现复杂的异步编程需求。

tanwu1的博客 1047

ESP32-IDF HTTP服务器实战:从零搭建Web控制界面

本文详细介绍了如何在ESP32平台上使用ESP-IDF框架从零搭建HTTP服务器,并创建Web控制界面。内容涵盖环境配置、服务器基础设置、Web页面处理、GPIO控制接口实现及AJAX异步请求处理,帮助开发者快速掌握物联网设备Web控制开发。

efc123456的博客 1063

Java8——异步编程

Java8——异步编程 异步编程 所谓异步其实就是实现一个无需等待被调用函数的返回值而让操作继续运行的方法 创建任务并执行任务 无参创建 CompletableFuture<String> noArgsFuture = new CompletableFuture<>(); 传入相应任务,无返回值 runAsync方法可以在后台执行异步计算,但是此时并没有返回值。持有...

weixin_43133353的博客 771

5QI(5G QoS Identifier)

此外,5QI与网络切片(Network Slicing)密切相关,网络切片允许网络运营商在物理网络内部创建虚拟网络,为不同的客户和用例提供定制化服务和QoS。5QI(5G QoS Identifier)在5G网络中用于标识不同的服务质量要求,它允许网络对不同的数据流进行优先级划分和资源分配。:5QI的实施需要对5G网络的QoS参数有深入的理解,包括延迟、吞吐量、数据包丢失率等,这对技术人员提出了较高的要求。不是所有的5G设备都支持5QI的全部功能,这可能会限制5QI应用的广泛性。

记录有价值的内容 6533

031、速度前馈与加速度前馈

摘要: 本文通过追剪机调试案例,阐述了速度前馈和加速度前馈在运动控制中的关键作用。前馈不是高级技巧,而是解决动态响应问题的必要手段:速度前馈通过预测速度变化提前补偿,加速度前馈则针对惯量变化提供力矩补偿。实现时需注意信号平滑处理,避免噪声放大。前馈与PID分工明确,前馈承担主要动态响应,PID处理残余误差。调试应先调前馈再微调PID,前馈增益需计算而非仅凭参数调整,并允许适当残余误差。前馈作为开环补偿不会影响系统稳定性,但需配合平滑的指令生成器使用。(149字)

m0_50546716的博客 292

Java8 CompletableFuture异步编程

介绍 使用场景 常用异步编程实现方案 - Thread - ExecutorService - CountDownLatch - CyclicBarrier - ForkJoinPool - CompletableFuture 各种实现方案总结 结构 结构梳理 - Future接口 - CompletionStage接口 常用方法 使用示例 1. 基本异步操作 2. 任务链式调用 3. 多个异步任务组合 4. 异常处理 5. 并行执行多个任务 6. 处理返回值的转换

骑个小蜗牛的博客 3602

AI Agent Harness与SCADA系统集成

简单来说,AI Agent Harness是Industrial AI Agent的“中央调度室+装备库+知识库+安全网”把不同技术栈、不同部署环境(云/边/端)、不同功能(感知、推理、决策、执行、验证)的“零散Agent”组装成具备特定工业场景能力的Agent Team(如聚合反应根因诊断Agent Team);管理Agent Team的生命周期(创建、初始化、调度、监控、销毁);提供统一的工业多工具调用接口(对接SCADA、MES、PLC、DCS、巡检机器人、数字孪生平台等);内置。

欢迎来到我的优快云空间!这里聚焦AI大模型应用实战,分享前沿技术、实战案例与开发经验。 355

Java8异步编程-CompletableFuture

异步编程的难点 如何优雅地实现异步编程一直都是一个难题,异步编程的通常做法就是采用callback的方法,但是这种方法通常会把代码嵌套在正常流程的代码中,而且当有多层嵌套的时候代码更加难以维护。 另外还有一点,异步编程的异常处理也是难以未维护,特别是在Java中,异步编程通常由新的线程完成,而子线程的异常是无法在父线程捕获的,那么对于异步执行结果的获取就需要付出更大的代价,比如通过:轮询、事件驱动等来完成。 从Future说起 Java5之后就引入Future用于异步编程,通过get()方法来对异步执行结果

m0_57983661的博客 1001

爬虫Incapsula reese84加密案例:Etihad航空(纯算法)

爬虫Incapsula reese84加密案例:Etihad航空,本文就不再讲解如何找加密参数、定位加密参数,以及分析加密的一个过程。就直接从扣算法开始,扣算法前先看之前讲解的加密函数生成的过程,文章会一个函数一个函数的讲解扣算法。fiddler根据规则替换本地文件。调试过程中遇到部分环境不一样是正常的,具体还要根据自己调试的js为准,就像之前补环境时是有二十一个循环,这次扣算法多出一个循环,不过大部分环境是一样的第一个函数:通过document.addEventListener添加了很多事件。

老衲爱洗头的博客 1993

第十五章数据管理成熟度评估【6’】(DAMA-CDGA 2022年以后历年模拟题真题汇总,基本包含所有考点。)

第十五章数据管理成熟度评估【6’】(DAMA-CDGA 2022年以后历年模拟题真题汇总,基本包含所有考点。)

JasonH2021的博客 405

Java 8 异步编程进阶:CompletableFuture 详解与最佳实践

/ 使用 supplyAsync 创建一个有返回值的异步任务,并指定自定义线程池// 模拟耗时任务try {// 阻塞等待任务完成并获取结果使用场景:适用于需要对线程池进行精细控制的场景,例如限制线程数量或使用特定的线程池策略。

shitou的博客 1178

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

理解Java8里面CompletableFuture异步编程

CompletableFuture在Java里面被用于异步编程,异步通常意味着非阻塞,可以使得我们的任务单独运行在与主线程分离的其他线程中,并且通过 回调可以在主线程中得到异步任务的执行状态,是否完成,和是否异常等信息。

DreamSun的博客 1053

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

java8-新的异步编程方式-初步

java8新的异步编程方式 package com.test.java8; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; /** * java8新的异步编程方式 * * @date 2018/12/28 17:17 */ public clas...

qq_38946877的博客 1054

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Java8 异步编程 CompletableFuture 精讲

本文大纲速看 一、异步编程 通常来说,程序都是顺序执行,同一时刻只会发生一件事情。如果一个函数依赖于另一个函数的结果,它只能等待那个函数结束才能继续执行,从用户角度来说,整个程序才算执行完毕。 但现在的计算机普遍拥有多核 CPU,在那里干等着毫无意义,完全可以在另一个处理器内核上干其他工作,耗时长的任务结束之后会主动通知你。这就是异步编程的出发点:充分使用多核 CPU 的优势,最大程度提高程序性能。 一句话来说:所谓异步编程,就是实现一个无需等待被调用函数的返回值而让操作继续运行的方法。 二、抛出一个问题

天道酬勤 2082

Java 8中的并行和异步编程

并行代码是在多个线程上运行的代码,曾经是许多经验丰富的开发人员的噩梦,但是Java 8带来了许多更改,这些更改应该使这种提高性能的技巧更加易于管理。 并行流 在Java 8之前,并行(或并发)代码与顺序代码之间存在很大差异。 调试非顺序代码也非常困难。 只需像通常那样设置一个断点并按照流程进行操作,就可以删除并行方面,如果这是导致该错误的原因,那么这是一个问题。 幸运的是,Java ...

最佳 Java 编程 2043

java8 异步调用_Java8学习之异步编程

异步编程所谓异步其实就是实现一个无需等待被调用函数的返回值而让操作继续运行的方法创建任务并执行任务无参创建CompletableFuture noArgsFuture = new CompletableFuture<>();传入相应任务,无返回值runAsync方法可以在后台执行异步计算,但是此时并没有返回值。持有一个Runnable对象。CompletableFuture noRet...

weixin_36289211的博客 801

java8 协程_Java8 异步编程—CompletableFuture

Java 为并发编程提供了众多的工具,本文将重点介绍 Java8 中 CompletableFuture。笔者在自己搜索资料及实践之后,避开已经存在的优秀文章的写作内容与思路,将以更加浅显的示例和语言,介绍 CompleatableFuture, 同时提供自己的思考。本文最后会附上其他优秀的文章链接供读者进行更详细学习与理解。1. 理解 Future当处理一个任务时,总会遇到以下几个阶段:提交任务...

weixin_31046947的博客 1004
上一篇: Linux 防火墙
下一篇: SpringBoot文件上传与校验
魅Lemon
博客等级 码龄8年 1388粉丝 195原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值