异步、线程池、以及CompletableFuture 组合式异步编排

初始化线程的4种方式

1-继承 Thread
2-实现 Runnable 接口
3-实现 Callable接口 + FutureTask (可以拿到返回结果,可以处理异常)
4-线程池
Executors.newFixedThreadPool(10);
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
 

以上四种方式的区别

  • 方式1和方式2:主进程无法获取线程的运算结果。不适合当前场景

  • 方式3:主进程可以获取线程的运算结果,但是不利于控制服务器中的线程资源。可以导致服务器资源耗尽。

  • 方式4:可以控制资源,系统性能稳定

下面展示下以上实现方式的DEMO代码

public class ThreadTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("main...start....");
        /**
         * 1. 继承 Thread
         *      Thread01 thread = new Thread01();
         *      thread.start();
         * 2. 实现 Runnable 接口
         *      Runable01 runable01 = new Runable01();
         *      new Thread(runable01).start();
         * 3. 实现 Callable接口 + FutureTask (可以拿到返回结果,可以处理异常)
         *      FutureTask<Integer> futureTask = new FutureTask<>(new Callable01());
         *      new Thread(futureTask).start();
         *      // 阻塞等待整个线程执行完成,获取返回结果
         *      Integer integer = futureTask.get();
         * 4. 线程池
         *      给线程池直接提交任务.
         *      我们以后在业务代码里面,以上三种启动线程的方式都不用。【将所有的多线程异步任务都交给线程池执行】
         * 区别:
         *      1、2不能得到返回值,3可以获取返回值
         *      1、2、3都不能控制资源
         *      4 可以控制资源,系统性能稳定
         */
    }

    public static class Thread01 extends Thread {
        @Override
        public void run() {
            System.out.println("当前线程: " + Thread.currentThread().getId());
            int i = 10/2;
            System.out.println("运行结果:" + i);
        }
    }

    public static class Runable01 implements Runnable {
        @Override
        public void run() {
            System.out.println("当前线程: " + Thread.currentThread().getId());
            int i = 10/2;
            System.out.println("运行结果:" + i);
        }
    }

    public static class Callable01 implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            System.out.println("当前线程: " + Thread.currentThread().getId());
            int i = 10/2;
            System.out.println("运行结果:" + i);
            return i;
        }
    }


}

一、线程池配置详解以及DEMO

 1-1线程池参数以及解释

 拒绝策略的详解

线程池中的任务队列已满且线程池中的线程数达到 maximumPoolSize 时,无法再提交新的任务。这种情况下,线程池就需要使用拒绝策略来处理这些无法处理的任务。Java 提供了四种默认的拒绝策略,具体如下:

  1. CallerRunsPolicy:当线程池无法处理新任务时,会把任务交给调用线程执行(即当前 execute() 方法所在的线程执行)。可以有效避免任务被丢失,但可能会影响调用线程的性能。

  2. AbortPolicy:当线程池无法处理新任务时,会直接抛出一个 RejectedExecutionException。这是默认的拒绝策略。

  3. DiscardPolicy:当线程池无法处理新任务时,会直接把任务丢弃,不做任何处理。

  4. DiscardOldestPolicy:当线程池无法处理新任务时,会把任务队列中最旧(即最先提交)的任务丢弃,然后重新尝试提交任务。

除了上述四种默认的拒绝策略外,也可以自定义一个拒绝策略。只需要实现 RejectedExecutionHandler 接口,并实现其中的 rejectedExecution() 方法即可。在该方法中,可以定义任何自定义的处理逻辑,比如记录日志、重试等。通过自定义拒绝策略来更好地控制任务的处理方式。

1-2 线程池测试DEMO

/**
 * @Description:
 * @Created: with IntelliJ IDEA.
 * @author: 夏沫止水
 * @createTime: 2020-06-18 11:16
 **/
/**
    1)、继承 Thread
    2)、实现 Runnable 接口
    3)、实现 Callable 接口 + FutureTask (可以拿到返回结果,可以处理异常)
    4)、线程池
 */
public class ThreadTest {


    // 当前系统池只有一两个,每个异步任务直接提交给线程池,让他自己去执行
    public static ExecutorService service = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws ExecutionException, InterruptedException {

         service.execute(new Runable01());
         Future<Integer> submit = service.submit(new Callable01());
         submit.get();



    }

    /**
     * <简述>ThreadPoolExecutor  线程池
     * <详细描述>
     *     运行流程:
     * 1、线程池创建,准备好 core 数量的核心线程,准备接受任务
     * 2、新的任务进来,用 core 准备好的空闲线程执行。
     * (1) 、core 满了,就将再进来的任务放入阻塞队列中。空闲的 core 就会自己去阻塞队
     * 列获取任务执行
     * (2) 、阻塞队列满了,就直接开新线程执行,最大只能开到 max 指定的数量
     * (3) 、max 都执行好了。Max-core 数量空闲的线程会在 keepAliveTime 指定的时间后自
     * 动销毁。最终保持到 core 大小
     * (4) 、如果线程数开到了 max 的数量,还有新任务进来,就会使用 reject 指定的拒绝策
     * 略进行处理
     * 3、所有的线程创建都是由指定的 factory 创建的。
     * 面试:
     * 一个线程池 core 7; max 20 ,queue:50,100 并发进来怎么分配的;
     * 先有 7 个能直接得到执行,接下来 50 个进入队列排队,在多开 13 个继续执行。现在 70 个
     * 被安排上了。剩下 30 个默认拒绝策略
     *
     *  new LinkedBlockingDeque<Runnable>(10000) 默认是 integer 最大值
     * @author syf
     * @date 2022/11/28 16:58
     */
    private static void threadPool() {
        //当前系统里面池 只能有几个
        ExecutorService threadPool = new ThreadPoolExecutor(
                10,//一直存在核心线程数(准备就绪的线程数量,等待异步线程执行)  相当于 new Thread(200),没有start
                50, //最大线程数 (控制资源并发)
                10L,//存活时间 1-如果当前线程数量大于核心数量,(释放空闲的线程,指的是大于核心的数量,核心一直不释放 maximumPoolSize - corePoolSize)
                TimeUnit.SECONDS,//时间单位
                new LinkedBlockingDeque<Runnable>(10000),//阻塞队列 如果任务很多,将目前多的任务放在队列里面,有线程空闲,就回去队列里面取新的执行
                Executors.defaultThreadFactory(),//线程的创建工厂
                new ThreadPoolExecutor.AbortPolicy()//RejectedExecutionHandler  如果队列满了,按照指定的拒绝策略拒绝任务
        );

        //定时任务的线程池
        ExecutorService service = Executors.newScheduledThreadPool(2);
    }



    public static class Runable01 implements Runnable {
        @Override
        public void run() {
            System.out.println("当前线程: " + Thread.currentThread().getId());
            int i = 10/2;
            System.out.println("运行结果:" + i);
        }
    }


    public static class Callable01 implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return i;
        }
    }

}

 1-2 常见的4种线程池

1. FixedThreadPool: 固定大小的线程池,一旦提交任务就会立即执行,当线程数达到线程池初始设置的数量时,任务会在队列中等待空闲线程来处理。
2. CachedThreadPool: 可缓存的线程池,线程池的数量不固定,会根据任务的多少自动调整。当任务量增加时,会自动添加新的线程来处理。当任务数减少时,也会销毁部分线程释放资源。
3. SingleThreadExecutor: 单线程线程池,所有任务都在同一线程中按照顺序执行,适用于需要按顺序执行任务的场景。
4. ScheduledThreadPool: 定时定期执行任务的线程池,可以根据需要定时执行任务或者周期性执行任务。适用于需要定期执行或者定时执行任务的场景。

二、CompletableFuture 组合式异步编排 

场景:
查询商品详情页的逻辑比较复杂,有些数据还需要远程调用,必然需要话费更多的时间,几个请求的内容以及相应时间如下:

  • 获取sku的基本信息 0.5s
  • 获取sku的图片信息 0.5s
  • 获取sku的促销信息 1s
  • 获取spu的所有销售属性 1s
  • 获取规格参数组及组下的规格参数 1.5s
  • spu详情 1s

 原因以及处理:

  1. 假如商品详情页的每个查询,需要如下标注的时间才能完成。那么,用户需要5.5s后才能看到商品详情页的内容。很显然是不能接受的。
  2. 如果有多个线程同时完成这6步操作,也许只需要1.5s即可完成响应
     

 2.1 创建异步对象

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

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

2.1.1 runAsync

runAsync是创建没有返回值的异步任务。它有如下两个方法,一个是使用默认线程池(ForkJoinPool.commonPool())的方法,一个是带有自定义线程池的重载方法

// 不带返回值的异步请求,默认线程池
public static CompletableFuture<Void> runAsync(Runnable runnable)
 
// 不带返回值的异步请求,可以自定义线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)

测试demo

public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
            System.out.println("do something....");
        });
 
        //等待任务执行完成
        System.out.println("结果->" + cf.get());
}
 
 
public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 自定义线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
            System.out.println("do something....");
        }, executorService);
 
        //等待任务执行完成
        System.out.println("结果->" + cf.get());
}

 结果:

2.2.2  supplyAsync

supplyAsync是创建带有返回值的异步任务。它有如下两个方法,一个是使用默认线程池(ForkJoinPool.commonPool())的方法,一个是带有自定义线程池的重载方法

// 带返回值异步请求,默认线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
 
// 带返回值的异步请求,可以自定义线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
 

 测试demo

public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
            System.out.println("do something....");
            return "result";
        });
 
        //等待任务执行完成
        System.out.println("结果->" + cf.get());
}
 
 
public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 自定义线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
            System.out.println("do something....");
            return "result";
        }, executorService);
 
        //等待子任务执行完成
        System.out.println("结果->" + cf.get());
}

结果:

 

 2.2  计算完成时回调

当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。主要是下面的方法:

//可以处理异常,无返回值
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
//可以处理异常,有返回值
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)

whenComplete 可以处理正常和异常的计算结果【感知】,exceptionally 处理异常情况【修改】
whenComplete 和 whenCompleteAsync 的区别:

whenComplete :是执行当前任务的线程继续执行 whenComplete 的任务
whenCompleteAsync : 是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行 

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

public static void main(String[] args) throws ExecutionException, InterruptedException {
    System.out.println("main...start....");
  /**
   * 方法完成之后的感知
   */
  CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
    System.out.println("当前线程: " + Thread.currentThread().getId());
    int i = 10 / 0;
    System.out.println("运行结果:" + i);
    return i;
  }, executor).whenComplete((result,exception)->{
    // 虽然能得到异常信息,但是没法修改返回数据
    System.out.println("异步任务成功完成了...结果是:" + result+";异常是:"+exception);
  }).exceptionally(throwable -> {
    // 可以感知异常同时返回指定默认值
    System.out.println(throwable);
    return 10;
  });
    System.out.println(future2.get());

    System.out.println("main...end....");
}

2.3 异步线程回调

2.3.1  线程串行化

thenRun 方法:
只要上面的任务执行完成,就开始执行 thenRun,只要处理完任务后,执行 thenRun 的后续操作
thenAccept 方法:
消费处理结果。接收任务的处理结果,并消费处理,无返回结果
thenApply 方法:
当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值。
 

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

 测试demo:

/**
 * 线程串行化
 *  1)、thrnRun :不能获取到上一步的执行结果,并无返回值
 *  2)、thenAccept : 能接收上一步结果,但是无返回值
 *  3)、thenApply :能接收上一步结果,并有返回值
 */
//thrnRun 
CompletableFuture<Void> future1 = CompletableFuture.supplyAsync(() -> {
    System.out.println("当前线程1: " + Thread.currentThread().getId());
    int i = 10 / 2;
    System.out.println("运行结果:" + i);
    return i;
}, executor).thenRunAsync(() -> {
    System.out.println("任务2启动了...");
}, executor);

//thenAccept 
CompletableFuture<Void> future2 = CompletableFuture.supplyAsync(() -> {
    System.out.println("当前线程2: " + Thread.currentThread().getId());
    int i = 10 / 2;
    System.out.println("运行结果:" + i);
    return i;
}, executor).thenAcceptAsync(res->{
    System.out.println("任务2启动了..." + res);
},executor);

//thenApply 
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    System.out.println("当前线程3: " + Thread.currentThread().getId());
    int i = 10 / 2;
    System.out.println("运行结果:" + i);
    return i;
}, executor).thenApplyAsync(res -> {
    System.out.println("任务2启动了..." + res);
    return "Hello" + res;
}, executor);
System.out.println("返回值:"+future.get());

 结果:

当前线程1: 20
运行结果:5
thrnRun任务2启动了...
当前线程2: 22
运行结果:5
thenAccept任务2启动了...5
当前线程3: 24
运行结果:5
thenApply任务2启动了...5
返回值:Hello5

 2.4 多任务组合

2.4.1 thenCombine、thenAcceptBoth 和runAfterBoth  两个任务组合都要完成

区别:

  • thenCombine会将两个任务的执行结果作为所提供函数的参数,且该方法有返回值;
  • thenAcceptBoth同样将两个任务的执行结果作为方法入参,但是无返回值;
  • runAfterBoth没有入参,也没有返回值。注意两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果。
     

 测试demo:

/**
 * 两个异步任务都完成
 */
//启动任务一
CompletableFuture<Integer> future01 = CompletableFuture.supplyAsync(() -> {
    System.out.println("任务1线程: " + Thread.currentThread().getId());
    int i = 10 / 4;
    System.out.println("任务1结束");
    return i;
}, executor);
//启动任务二
CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> {
    System.out.println("任务2线程: " + Thread.currentThread().getId());
    System.out.println("任务2结束");
    return "Hello";
}, executor);

future01.runAfterBothAsync(future02, ()->{
    System.out.println("任务3开始...");
},executor);

future01.thenAcceptBothAsync(future02, (f1,f2)->{
    System.out.println("任务3开始,之前的结果:f1="+f1+";f2="+f2);
},executor);

CompletableFuture<String> future = future01.thenCombineAsync(future02, (f1, f2) -> {
    return f1 + ":" +f2 +"->HaHa";
}, executor);
System.out.println("方法3的返回结果:" + future.get());

结果:

任务1线程: 20
任务1结束
任务2线程: 21
任务2结束
任务3开始...
任务3开始,之前的结果:f1=2;f2=Hello
方法3的返回结果:2:Hello->HaHa

 2.4.2 applyToEither、acceptEither和runAfterEither 两个任务只要有一个完成,就执行第三个

区别:

  • applyToEither会将已经完成任务的执行结果作为所提供函数的参数,且该方法有返回值
  • acceptEither同样将已经完成任务的执行结果作为方法入参,但是无返回值;
  • runAfterEither没有入参,也没有返回值。

 注意:两任务组合-只要有一个任务完成就执行第三个,两个 CompletionStage 的返回类型要一致

 测试demo:

/**
         * 两个异步任务只要有一个完成,我们就执行任务3
         *  runAfterEither: 不感知结果,自己也没有返回值
         *  acceptEither : 感知结果,自己没有返回值
         *  applyToEitherAsync: 感知结果,并且有返回值
         */
        //任务一
        CompletableFuture<Object> future01 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务1线程: " + Thread.currentThread().getId());
            int i = 10 / 4;
            System.out.println("任务1结束");
            return i;
        }, executor);
        //任务二
        CompletableFuture<Object> future02 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务2线程: " + Thread.currentThread().getId());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务2结束");
            return "Hello";
        }, executor);

//        CompletableFuture<Void> future3 = future01.runAfterEitherAsync(future02, () -> {
//            System.out.println("任务3开始...");
//        }, executor);
//        CompletableFuture<Void> future4 = future01.acceptEitherAsync(future02, (res) -> {
//            System.out.println("任务3开始..." + res);
//        }, executor);
        CompletableFuture<String> future = future01.applyToEitherAsync(future02, res -> {
            System.out.println("任务3开始..." + res);
            return res.toString() + "-->haha";
        }, executor);

        System.out.println("main...end...." + future.get());

执行结果:

runAfterEitherAsync:

任务1线程: 20
任务1结束
任务2线程: 21
任务3开始...
 

 acceptEitherAsync

任务1线程: 20
任务1结束
任务2线程: 21
任务3开始...2
任务2结束 

applyToEitherAsync:有返回值

任务1线程: 20
任务1结束
任务2线程: 21
任务3开始...2
main...end....2-->haha
任务2结束
 

 2.5  allOf / anyOf 

2.5.1 allOf :等待所有任务完成

   CompletableFuture是多个任务都执行完成后才会执行,只有有一个任务执行异常,则返回的CompletableFuture执行get方法时会抛出异常,如果都是正常执行,则get返回nul

测试代码 

//任务一
CompletableFuture<String> futureImg = CompletableFuture.supplyAsync(() -> {
    System.out.println("查询商品图片信息");
    return "hllo.jpg";
}, executor);
//任务二
CompletableFuture<String> futureAttr = CompletableFuture.supplyAsync(() -> {
    System.out.println("查询商品的属性");
    return "星空白+256G";
}, executor);
//任务三
CompletableFuture<String> futureDesc = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("查询商品的介绍");
    return "苹果";
}, executor);
//allOf
CompletableFuture<Void> allOf = CompletableFuture.allOf(futureImg, futureAttr, futureDesc);
allOf.get();    // 等待所有结果完成

System.out.println("main...end...." );

 结果:

如上述代码,等待任务三sleep结束,任务才会结束。

查询商品图片信息
查询商品的属性
查询商品的介绍
main...end....

将任务三改为异常,如下图运行情况

 

2.5.2 anyOf :只有一个任务完成

CompletableFuture是多个任务只要有一个任务执行完成,则返回的CompletableFuture执行get方法时会抛出异常,如果都是正常执行,则get返回执行完成任务的结果。

 测试代码

        //任务一
        CompletableFuture<String> futureImg = CompletableFuture.supplyAsync(() -> {
            System.out.println("查询商品图片信息");
            return "hllo.jpg";
        }, executor);
        //任务二
        CompletableFuture<String> futureAttr = CompletableFuture.supplyAsync(() -> {
            System.out.println("查询商品的属性");
            return "星空白+256G";
        }, executor);
        //任务三
        CompletableFuture<String> futureDesc = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000);
                int a = 1/0;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("查询商品的介绍");
            return "苹果";
        }, executor);
        CompletableFuture<Object> allOf = CompletableFuture.anyOf(futureImg, futureAttr, futureDesc);
        allOf.get();    // 等待所有结果完成

        System.out.println("main...end...." );

任务三异常情况下,有完成一样可以结束

查询商品图片信息
查询商品的属性
main...end....

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

syfjava

请博主喝杯蜜雪冰城

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值