Java多线程【异步编排】

目录

    • 1. 线程和进程
      • 1.1 进程
      • 1.2 线程
    • 2. 多线程实践
      • 2.1 多线程的实现方式
      • 2.2 线程池
        • 1. 线程池两种构建方式
        • 2. 线程池核心参数解析
        • 2. 如何合理配置线程池的参数?
        • 3. 队列的使用
    • 3. 异步计算
      • 3.1 什么是异步?
      • 3.2 Future
      • 3.3 CompletableFuture 组合式异步编排
        • 3.3.1 业务场景
        • 3.3.2 创建异步对象
        • 3.3.3 线程串行执行
        • 3.3.4 线程AND聚合执行
        • 3.3.5 线程OR聚合执行
        • 3.3.6 多任务组合执行
        • 3.3.7 异常处理

1. 线程和进程

1.1 进程

本质是一个运行的程序,是系统资源分配的基本单位,每一个进程都有一个独立的内存地址空间:(包括代码空间,数据空间),每一个进程内存空间,数据空间都是私有的,不能被其他的进程访问:进程与进程之间相互隔离的。

1.2 线程

线程是为了让CPU执行更高效提出的。

  • 进程中的所有资源都被线程共享
  • 线程是CPU分配的基本单位
  • 线程之间操作共享资源进行通信。
  • 线程比进程通信更简单、高效

1)上下文切换

问题1: 现在的机器可以运行远大于CPU核心数的线程运行,操作资源是如何分配的?

CPU采用时间分片的模式,将CPU的时间片段轮流分配给多个线程执行;分配给每个线程的时间大概是几十毫秒ms,线程在CPU时间片执行,如果当前线程没有执行结束,CPU时间分片已经结束,此线程就会被挂起,下一个时间分片将会分给下一个线程,上一个线程等待唤醒。

问题2: 线程没有执行完毕,线程别挂起,等待下次被唤醒,继续完成任务!系统是怎么知道线程之前运行到哪里?从哪里开始执行呢??

CPU包含

  • 寄存器(存储数据状态信息)
  • 程序计数器:存储CPU正在执行的指令,以及执行下一条指令的位置。

CPU在执行任务时候,都必须依赖寄存器,程序计数器;这些东西就是CPU切换的开销,称之为上下文切换

2)线程的调度

  1. 抢占式调度
  2. 协同式调度

问题1:操作系统如何共享CPU时间分片,如何分配CPU执行权限?线程何时分到时间分片?线程分配多长时间?如何给重要的线程多分配一点时间,次要的少分配点时间呢?

(1)抢占式调度

并发线程下,所有线程会抢占时间分片,获取执行权限。有些线程执行时间长,造成线程堵塞等待,等待CPU资源。

Java多线程【异步编排】_第1张图片

JVM线程调度:抢占式调度模式

(2)协同式调用

一个线程执行完成后主动通知系统切换到另一个线程执行;(同步堵塞)

致命缺点:一个线程堵塞,则会导致整个进程堵塞,一个线程异常,则会导致整个进程崩溃

3)并行和并发

  • 并发:一段时间内,多个线程轮流分配时间分片,抢占式执行。
  • 并行:同一时刻,多个线程同时执行。

2. 多线程实践

2.1 多线程的实现方式

  1. 继承Thread类,实现多线程

    public static void main(String[] args) { 
        System.out.println("thread.........start...........");
        /* 1. 测试Thread实现的多线程 */
        // 创建多线程对象
        ThreadA threadA = new ThreadA();
        // 开启线程
        threadA.start();
        System.out.println("thread.........end.............");
    }
    
    
    public static class ThreadA extends Thread {
        /**
         * run方法是线程执行主体,多线程任务在run方法中执行。
         */
        @Override
        public void run() {
            log.info("继承Thread实现方式.....");
            // 业务代码执行
            int i = 100 / 3;
            log.info("业务代码执行的结果为:{},线程的名称:{},线程的ID:{}", i, this.getName(), this.getId());
        }
    }
    

    总结:多线程线程的执行,是在主线程执行完毕后再执行的。(体现了异步的效果)

  2. 实现Runnable接口,实现多线程

    (1)普通构建方式

    public static void main(String[] args) { 
        System.out.println("thread.........start...........");
        /* 测试实现Runnable接口,实现多线程 */
        // 创建多线程对象
        Thread02 thread02 = new Thread02();
        // 构建多线程对象
        Thread thread = new Thread(thread02);
        // 开启线程
        thread.start();
        System.out.println("thread.........end.............");
    }
    
    public static class Thread02 implements Runnable {
        /**
         * run方法是线程执行主体,多线程任务在run方法中执行。
         */
        @Override
        public void run() {
            log.info("实现Runnable接口的实现方式.....");
            // 业务代码执行
            int i = 100 / 3;
            log.info("业务代码执行的结果为:{}", i);
        }
    }
    

    (2)匿名内部类实现方式

    public static void main(String[] args) { 
        System.out.println("thread.........start...........");
        /* 测试实现Runnable接口,实现多线程 */
        // 创建多线程对象
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                log.info("实现Runnable接口的实现方式.....");
                // 业务代码执行
                int i = 100 / 3;
                log.info("业务代码执行的结果为:{}", i);
            }
        };
        // 构建多线程对象
        Thread thread = new Thread(runnable);
        // 开启线程
        thread.start();
        System.out.println("thread.........end.............");
    }
    

    (3)lambda表达式方式(推荐使用,但需要了解下lambda表达式实现原理)

    public static void main(String[] args) { 
        System.out.println("thread.........start...........");
        /* 测试实现Runnable接口,实现多线程 */
        // lambda创建多线程对象,直接传入Thread构造参数中,并开启线程
        new Thread(() -> {
            log.info("实现Runnable接口的实现方式.....");
            // 业务代码执行
            int i = 100 / 3;
            log.info("业务代码执行的结果为:{}", i);
        }).start();
        System.out.println("thread.........end.............");
    }
    
    

    lambda表达式的特点:
    1. @FunctionalInterface:表示可以使用lambda表达式编程,此注解相当于一个标识
    2. 接口如果只有一个方法(必须满足),即使没有上面的注解,也可以使用lambda表达式;程序会在后台自动识别。

    lambda表达式写作形式:方法括号(有参写,无参不写) -> {业务执行方法体}

  3. Callable + FutureTask实现多线程

    jdk1.5后: 添加Callable接口,实现多线程,相较于Thread和Runnable接口而言,Callable有返回值。

    @FunctionalInterface :表示支持lambda表达式
    public interface Callable

    1. 具有泛型的接口,只有一个call方法,call方法就是多线程执行业务主体。
    2. 方法有返回值,返回值类型就是指定的泛型类型。

    疑问: 由于多线程执行必须和Thread有关系,Callable和Thread有什么关系呢?

    1. 因为Runnable的实现类FutureTask即创建了Runnable接口的构造函数,也创建了Callable接口的构造函数

    2. 同时Thread类需要的是一个对Runnable接口的实现类,而RunnableFuture是继承于Runnable的,FutureTaskRunnableFuture实现类,所以通过FutureTask构造的多线程对象可以直接传入Thread使用。。

    Java多线程【异步编排】_第2张图片

    (1)普通实现方式

    public static void main(String[] args) {
        System.out.println("thread.........start...........");
        // 创建多线程对象
        Thread03 thread03 = new Thread03();
        // 创建FutureTask对象,将thread03对象传递到构造函数中
        FutureTask<Integer> task = new FutureTask<>(thread03);
        // 创建多线程对象
        Thread thread = new Thread(task);
        // 开启线程执行
        thread.start();
    
        try {
            // 等待子线程执行结束后,获取返回结果,该代码为同步阻塞,必须等待异步线程执行结束,且返回结果后,才能继续往下执行。
            Integer integer = task.get();
            System.out.println("子程序运行结果为:" + integer);
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
        System.out.println("thread.........end.............");
    }
    
    
    public static class Thread03 implements Callable<Integer> {
        /**
         * 业务执行主体
         * @return 返回值
         * @throws Exception 异常捕获
         */
        @Override
        public Integer call() throws Exception {
            log.info("实现Callable接口的实现方式.....");
            // 业务代码执行
            int i = 100 / 3;
            log.info("业务代码执行的结果为:{}", i);
            return i;
        }
    }
    

    (2)匿名内部类实现方式

    public static void main(String[] args) {
       System.out.println("thread.........start...........");
       // 创建多线程对象
       Callable<Integer> callable = new Callable<Integer>() {
           @Override
           public Integer call() throws Exception {
               log.info("实现Callable接口的实现方式.....");
               // 业务代码执行
               int i = 100 / 3;
               log.info("业务代码执行的结果为:{}", i);
               return i;
           }
       };
       // 创建FutureTask对象,将Callable接口对象传递到构造函数中
       FutureTask<Integer> task = new FutureTask<>(callable);
       // 创建多线程对象
       Thread thread = new Thread(task);
       // 开启线程执行
       thread.start();
       try {
           // 等待子线程执行结束后,获取返回结果,该代码为同步阻塞,必须等待异步线程执行结束,且返回结果后,才能继续往下执行。
           Integer integer = task.get();
           System.out.println("子程序运行结果为:" + integer);
       } catch (InterruptedException | ExecutionException e) {
           throw new RuntimeException(e);
       }
    
       System.out.println("thread.........end.............");
    }
    

    (3)lambda表达式实现方式

    public static void main(String[] args) {
        System.out.println("thread.........start...........");
        // 创建FutureTask对象,通过lambda表达式实现Callable接口
        FutureTask<Integer> task = new FutureTask<>(() -> {
            log.info("实现Callable接口的实现方式.....");
            // 业务代码执行
            int i = 100 / 3;
            log.info("业务代码执行的结果为:{}", i);
            return i;
        });
        // 创建多线程对象
        Thread thread = new Thread(task);
        // 开启线程执行
        thread.start();
        try {
            // 等待子线程执行结束后,获取返回结果,该代码为同步阻塞,必须等待异步线程执行结束,且返回结果后,才能继续往下执行。
            Integer integer = task.get();
            System.out.println("子程序运行结果为:" + integer);
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
    
        System.out.println("thread.........end.............");
    }
    

2.2 线程池

问题1: 什么是线程池?

通俗易懂的来讲,就是事先准备好一些资源,有人要用(业务系统需要使用线程),就来线程池里拿(从线程池里获取线程),用完后还不能销毁,必须还给我(线程池的可复用性)。

问题2: 为什么要使用线程池?(池化技术)

问题3可以得出,线程的创建和销毁时很浪费时间和系统资源的,所以需要通过线程池来提高效率。

  1. 降低系统的资源消耗
  2. 提高系统的响应速度
  3. 方便管理(线程复用、控制最大并发数、管理线程)

问题3: 执行一个Java任务,可以直接new Thread来运行任务,线程从创建到消耗经历了哪些过程?

  1. 创建Java线程实例,线程是一个实例对象,堆内存中分配内存空间(创建线程需要消耗内存和时间);
  2. 执行start方式启动线程,操作系统为Java线程创建对应的内核线程,线程处于就绪状态(内核线程是操作系统的资源,创建需要时间和内存)
  3. 线程被操作系统CPU调度器选中后,线程开始执行(run方法开始运行)
  4. JVM开始为线程创建线程私有资源:JVM虚拟机栈 * 程序计数器(需要时间和内存)
  5. 线程运行过程中,CPU上下文切换(消耗时间,频繁切换,影响性能)
  6. 线程运行完毕,Java线程被垃圾回收器回收(销毁线程内存需要时间)

1. 线程池两种构建方式

  1. Executors(不推荐,最大线程数不可控)
  2. new ThreadPoolExecutor();(推荐,稳定可控)

基本构建方式

// 1.创建线程池对象;创建单个线程的线程池对象
ExecutorService executorService1 = Executors.newSingleThreadExecutor();
// 线程执行
try {
    for (int i = 0; i < 10; i++) {
        executorService1.execute(() -> {
            log.info("通过Executors创建线程池的方式实现多线程.....");
            // 业务代码执行
            int num = 100 / 3;
            log.info("业务代码执行的结果为:{}", num);
        });
    }
} catch (Exception e) {
    throw new RuntimeException(e);
} finally {
    // 线程池用完了,关闭线程池
    executorService1.shutdown();
}

线程池的分类

// 1.创建线程池对象;创建单个线程的线程池对象
ExecutorService executorService1 = Executors.newSingleThreadExecutor();

// 2.创建固定数量的线程池(指定核心线程数数量),核心线程数为2
ExecutorService executorService2 = Executors.newFixedThreadPool(2);

// 3.创建一个按照计划执行的线程池
ScheduledExecutorService executorService3 = Executors.newScheduledThreadPool(2);

// 4.创建一个自动增长的线程池
ExecutorService executorService4 = Executors.newCachedThreadPool();

警告: 任何时候都不建议使用Executor创建线程池方式,因为这样创建的线程池会无限制的增长线程池大小,从而导致内存被占满,线程量大导致性能严重下降,甚至OOM!

解决方案: 使用ThreadPoolExecutor类,来可控的创建线程池。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) 

2. 线程池核心参数解析

  1. corePoolSize: 线程池的核心线程数;初始化线程池时,会创建核心线程,进入等待状态,核心线程不会被销毁,提供线程的复用

  2. maximumPoolSize: 最大线程数;核心线程用完了,必须新建线程执行任务,但是新建线程数不会超过最大线程数

  3. keepAliveTime: 线程的存活时间,除了核心线程以外(maximumPoolSize - corePoolSize)的线程存活时间;当线程处于空闲状态,他可以活多久。

  4. unit: 存活时间单位

  5. workQueue: 任务阻塞队列,任务可能会很多,线程只有几个,因此可以将多余的任务放进队列进行缓冲,队列采用FIFO的,等待线程空闲,再从队列中取出任务执行。

  6. threadFactory: 线程工厂,默认使用defaultThreadFactory用来创建线程,一般使用默认即可。

  7. RejectedExecutionHandler: 线程池拒绝策略

    四种拒绝策略

    1. AbortPolicy: 新任务直接被拒绝,抛出异常:RejectedExecutionException
    2. DisCardPolicy: 队列满了,新任务忽略不执行,直接丢弃;
    3. DisCardOldestPolicy: 队列满了,丢弃任务队列中的头结点,通常是存活时间最长(等待最久)的任务,新任务直接添加到队列中,也不会抛出异常;
    4. CallerRunsPolicy: 新任务来临后,直接使用调用者所在线程执行任务即可(谁提交,谁执行)。

2. 如何合理配置线程池的参数?

合理配置线程相关的参数:核心线程数、最大线程数。

设置线程池的线程数量:根据业务类型进行设置(CPU密集型,IO密集型)

  • CPU密集型:所有任务都在内存中执行,没有磁盘的读写;该类型建议线程池最大数量设置为N(CPU核心数量)+ 1。
  • IO密集型:大量的磁盘读写任务;IO操作时,CPU处于空闲状态,最大线程数应该设置为:2N + 1。

最大线程数设置公式: (任务执行时间 / 任务CPU时间) * N(CPU核心数)

3. 队列的使用

在Java并发编程中,使用线程池,还必须使用任务队列,让任务在队列中进行缓冲:可以线程执行防洪泄流的效果,提升线程池处理任务能力;

通常在线程池中,使用的都是阻塞队列;

Java多线程【异步编排】_第3张图片

问题: 如何选择一个合适的队列呢?

Java多线程【异步编排】_第4张图片

基于Java的一些队列特性:

  1. ArrayBlockingQueue:基于数组实现的有界阻塞队列(有界的队列)
  2. LinkedBlockingQueue:基于链表实现的有界阻塞队列
  3. PriorityBlockingQueue:支持按照优先级排序无界阻塞队列
  4. DelayQueue:基于优先级实现无界阻塞队列
  5. SynchronousQueue只存储一个元素,如果该元素未被消费,就不能向里面存储元素。
  6. LinkedTransferQueue:基于链表实现的无界阻塞队列
  7. LinkedBlockingDeque:基于链表实现实现的双向的无界阻塞队列。

推荐: 建议使用有界的队列。

原因: 有界队列能增加系统的稳定性,根据需求设置大一些(可控设置);如果设置为无界队列,遇到不可控的因素,可能会导致队列中的任务越来越多,出现OOM,撑爆整个系统

3. 异步计算

3.1 什么是异步?

异步调用实现一个不需要被等等的方法的返回值;让调用者继续执行(异步执行);在java中,简单的讲就是开启另一个线程完成程序计算,使得调用者继续执行,不需要等等计算的结果,但是调用者仍然需要获取线程的计算结果(不需要同步阻塞等待)。

Java多线程【异步编排】_第5张图片

3.2 Future

Future是一个异步计算结果返回接口,目的获取返回值结果。但是future在获取返回值结果的时候,方法必须同步阻塞等待返回值结果。

  • get():获取结果(等待,阻塞)
  • isDone:判断任务是否已经完成(轮询等待)

Future对于获取结果来说是非常不方便的,只能通过同步阻塞的方式获取结果,或者是轮训得方式获取结果;同步阻塞与异步思想相违背,轮训又很占用CPU资源,不能及时得到结果,所以JDK8中设计出了CompletableFuture

CompletableFuture 是对 Future 的改进

3.3 CompletableFuture 组合式异步编排

CompletableFuture的作用:帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调函数的方式处理计算结果。

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {		

CompletableFuture的实现类可以看出,CompletableFuture具有Future的特性,还具备了CompletionStage的特性:串行执行,并行执行,聚合(AND聚合,OR聚合)

3.3.1 业务场景

由于查询CSDN作者详情页的业务逻辑复杂,数据的获取需要远程调用,这导致运行起来十分耗时,例如下面

  1. 获取作者头像----0.03s
  2. 获取作者分类专栏----0.5s
  3. 获取所有文章列表----1.8s
  4. 获取作者兴趣领域列表----0.8s

如果CSDN作者详情页的查询都需要以上标注时间才能完成。那用户需要3.13s后才能看到作者详情页的内容,很明显是不能接受的。如果有多个线程同时完成这4步操作,也许只需要1.8s即可完成响应。

3.3.2 创建异步对象

CompletableFuture 提供了四个静态方法来创建一个异步操作。

// runAsync方法不支持返回值。
public static CompletableFuture<Void> runAsync(Runnable runnable);
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);
// supplyAsync可以支持返回值。
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

没有指定Executor的方法会使用 ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。以下所有的方法都类同。

3.3.3 线程串行执行

带有 Async 默认是异步执行的。

  • 不带 Asycn :共同同一个线程
  • 带 Asycn : 交给线程池来进行执行

接口介绍:

// thenRun:类似流水线,前面的任务完成后,就开始执行thenRun的内容。(无传参,无返回值)
public CompletableFuture<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action, Executor executor);

// thenAccept:接收上一个任务的处理结果,并进行消费处理,无返回值。(有传参,无返回值)
public CompletableFuture<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor);

// thenApply:接收上一个任务的处理结果,并返回当前任务的返回值。(有传参,有返回值)
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor);

// thenCompose:接收上一个任务的处理结果,并且能够执行流水线式的第二个CompletionStage。(有传参,有返回值)
public <U> CompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor);

测试代码:

/**
 * 线程串行化
 *  1. thrnRun :不能获取到上一步的执行结果,并无返回值
 *  2. thenAccept : 能接收上一步结果,但是无返回值
 *  3. thenApply :能接收上一步结果,并有返回值
 *  4. thenCompose :接连处理第二个CompletionStage,能接收上一步结果,并有返回值。
 */
/* thenRun */
CompletableFuture<Void> thenRunFuture = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 2;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
}).thenRun(() -> {
    log.info("thenRun子线程开始运行.............");
});
// 调用执行
thenRunFuture.get();

/* thenAccept */
CompletableFuture<Void> thenAcceptFuture = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 2;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
}).thenAccept((t) -> {
    log.info("thenAccept子线程开始运行,参数是:{}", t);
    long res = t * 5L;
    log.info("计算结果是:{}", res);
});
// 调用执行
thenAcceptFuture.get();

/* thenApply */
CompletableFuture<Long> thenApplyFuture = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 2;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
}).thenApply((t) -> {
    log.info("thenAccept子线程开始运行,参数是:{}", t);
    long res = t * 5L;
    log.info("计算结果是:{}", res);
    return t * 5L;
});
// 调用执行
Long aLong = thenApplyFuture.get();
log.info("最终计算结果是:{}", aLong);

/* thenCompose */
// 第一个 CompletionStage
CompletableFuture<Long> thenComposeFuture = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 2;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
}).thenCompose(new Function<Integer, CompletionStage<Long>>() {
    @Override
    public CompletionStage<Long> apply(Integer t) {
        // 第二个 CompletionStage
        CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
            log.info("thenCompose子线程开始执行,参数是:{}", t);
            long res = t * 5L;
            log.info("计算结果:{}", res);
            return res;
        });
        return future;
    }
});
// 调用执行
Long aLong = thenComposeFuture.get();
log.info("最终计算结果是:{}", aLong);

3.3.4 线程AND聚合执行

特点: 两个任务组合起来,并且都要完成。

接口介绍:

// runAfterBoth:将两个CompletionStage任务组合起来,无需运行结果,两个任务运行完后,运行该任务。(无传参,无返回值)
public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor);

// thenAcceptBoth:将两个任务的结果交给thenAcceptBoth来处理。(有传参,无返回值)
public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action,Executor executor);

// thenCombine:将两个CompletionStage任务组合起来,并将两个任务的结果交给thenCombine来处理。(有传参,有返回值)
public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);

测试代码:

/**
 * 线程聚合
 *  1. runAfterBoth :不能接收两个任务的执行结果,并无返回值
 *  2. thenAcceptBoth : 能接收两个任务的执行结果,但是无返回值
 *  3. thenCombine :能接收两个任务的执行结果,并有返回值
 */
/* runAfterBoth */
// 第一个 CompletionStage
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 2;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
});
// 第二个 CompletionStage
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 3;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
});
// 利用runAfterBoth方法对f1,f2进行合并操作
CompletableFuture<Void> f = f1.runAfterBoth(f2, () -> {
    log.info("有个任务在执行:runAfterBoth方法正在运行.......");
});
// 调用执行
f.get();

/* runAfterBoth */
// 第一个 CompletionStage
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 2;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
});
// 第二个 CompletionStage
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 3;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
});
// 利用thenAcceptBoth方法对f1,f2进行合并操作,并输出f1,f2的执行结果
CompletableFuture<Void> f = f1.thenAcceptBoth(f2, (t, u) -> {
    log.info("第一个 CompletableFuture 执行结果:{}", t);
    log.info("第二个 CompletableFuture 执行结果:{}", u);
});
// 调用执行
f.get();

/* thenCombine */
// 第一个 CompletionStage
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 2;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
});
// 第二个 CompletionStage
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 3;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
});
// 利用thenCombine方法对f1,f2进行合并操作,获取f1,f2的执行结果,并返回f1和f2的运算结果
CompletableFuture<Integer> f = f1.thenCombine(f2, (t, u) -> {
    log.info("第一个 CompletableFuture 执行结果:{}", t);
    log.info("第二个 CompletableFuture 执行结果:{}", u);
    return t+u;
});
// 调用执行
Integer integer = f.get();
log.info("最终计算结果是:{}", integer);

3.3.5 线程OR聚合执行

特点: 两个任务组合起来,只要有一个任务完成就执行第三个。

接收介绍:

// runAfterEither:两个任务有一个任务完成,无需获取任务执行结果,处理runAfterEither任务。(无传参,无返回值)
// 两个CompletionStage,任何一个完成了都会执行下一步的操作(Runnable)
public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor);

// acceptEither:两个任务有一个任务完成,获取任务执行结果,处理acceptEither任务。(有传参,无返回值)
// 两个CompletionStage,谁执行返回的结果快,就用那个CompletionStage的结果进行下一步的消费操作。
public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action, Executor executor);

// applyToEither:两个任务有一个任务完成,获取任务执行结果,处理applyToEither任务。(有传参,有返回值)
// 两个CompletionStage,谁执行返回的结果快,就用那个CompletionStage的结果进行下一步的转化操作。
public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn, Executor executor);

测试代码:

/**
 * 线程聚合
 *  1. runAfterEither :不能接收上个任务的执行结果,并无返回值
 *  2. acceptEither : 能接收上个任务的执行结果,但是无返回值
 *  3. applyToEither :能接收上个任务的执行结果,并有返回值
 */
/* runAfterEither */
// 第一个 CompletionStage
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 2;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
});
// 第二个 CompletionStage
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 3;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
});
// 利用runAfterEither,在f1,f2执行完成后执行runAfterEither任务
CompletableFuture<Void> f = f1.runAfterEither(f2, () -> {
    log.info("有个任务在执行:runAfterEither方法正在运行.......");
});
// 调用执行
f.get();


/* acceptEither */
// 第一个 CompletionStage
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 2;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
});
// 第二个 CompletionStage
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 3;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
});
// 利用acceptEither获取上一个任务的执行结果,并执行acceptEither的任务
CompletableFuture<Void> f = f1.acceptEither(f2, (t) -> {
    log.info("acceptEither子线程正在运行,传递的参数是:{}", t);
});
// 调用执行
f.get();

/* applyToEither */
// 第一个 CompletionStage
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 2;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
});
// 第二个 CompletionStage
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start...........");
    int i = 10 / 3;
    log.info("线程名称:{},线程的执行结果为:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end.............");
    return i;
});
// 利用applyToEither获取上一个任务的执行结果,并执行applyToEither的任务,返回结果值
CompletableFuture<Integer> f = f1.applyToEither(f2, (t) -> {
    log.info("acceptEither子线程正在运行,传递的参数是:{}", t);
    return t * 5;
});
// 调用执行
Integer integer = f.get();
log.info("最终计算结果是:{}", integer);

3.3.6 多任务组合执行

// allOf : 等待所有任务完成
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs);
// anyOf :只有一个任务完成
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs);

allOf测试代码:

// 第一个 CompletionStage
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> {
    log.info("查看CSDN作者头像");
    return 1;
});
// 第二个 CompletionStage
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> {
    log.info("查看作者分类列表");
    return 2;
});
// 第三个 CompletionStage
CompletableFuture<Integer> f3 = CompletableFuture.supplyAsync(() -> {
    log.info("查看作者文章列表");
    return 3;
});
// 利用allOf,按照CompletableFuture定义顺序,统一执行
CompletableFuture<Void> allOf = CompletableFuture.allOf(f2, f1, f3);
// 调用执行
allOf.get();

image-20221013204340286

anyOf测试代码:

// 第一个 CompletionStage
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    return 1;
});
// 第二个 CompletionStage
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(50);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    return 2;
});
// 第三个 CompletionStage
CompletableFuture<Integer> f3 = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    return 3;
});
// 通过anyOf调用,获取率先完成的任务结果。
CompletableFuture<Object> anyOf = CompletableFuture.anyOf(f1, f2, f3);
// 调用执行
System.out.println(anyOf.get());

image-20221013204401719

3.3.7 异常处理

// exceptionally:处理异常情祝,同时返回默认值。
public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn);

// whenComplete:可以处理正常和异常的计算结果,虽然能得到异常信息,但是没法修改返回的数据。    
public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action,Executor executor);

// handle:类似于try{}finally{},可对结果做最后的处理(可处理异常),可改变返回值。
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);

代码测试:

/* exceptionally */
CompletableFuture<Integer> f = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start............");
    int i = 10 / 0;
    log.info("线程名称:{},线程执行结果:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end............");
    return i;
}).exceptionally((t)->{
    log.info("业务执行失败:{}",t.getMessage());
    return null;
});

/* whenComplete */
CompletableFuture<Integer> f = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start............");
    int i = 10 / 0;
    log.info("线程名称:{},线程执行结果:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end............");
    return i;
}).whenComplete((t, u) -> {
    log.info("上一步执行结果:{}",t);
    // 判断
    if(u!=null){
        log.info("执行错误,有异常:{}",u.getMessage());
    }
});

Integer integer = f.get();
log.info("最终执行结果:{}",integer);

/* handle */
CompletableFuture<Integer> f = CompletableFuture.supplyAsync(() -> {
    log.info("子线程future线程start............");
    int i = 10 / 2;
    log.info("线程名称:{},线程执行结果:{}", Thread.currentThread().getName(), i);
    log.info("子线程future线程end............");
    return i;
}).handle((t, u) -> {
    int res = -1;
    if (u != null) {
        log.info("执行错误:{}", u.getMessage());
    } else {
        res = t * 5;
    }
    return res;
});

Integer integer = f.get();
log.info("最终执行结果:{}",integer);

你可能感兴趣的:(Java基础,多线程,JVM,java,开发语言,多线程)