JUC并发编程-CompletableFuture

一、基础知识

1.1 并行、并发的区别

并发关注的是系统处理多个任务的能力,这些任务在同一时间段内交替执行,可能共享资源,而并行关注的是多个任务在同一时刻同时执行的能力,通常需要多个处理单元来实现,旨在提高性能和吞吐量。

1.2 进程、线程、管程的区别

进程是操作系统资源分配的基本单位,拥有独立的内存空间和系统资源,线程是进程的执行单元,共享进程的资源,而管程(Monitor)是一种同步机制,用于控制多个线程对共享资源的访问,确保线程安全。

1.3 线程的分类:

用户线程:如果没有明确的配置都默认为用户线程,是系统工作线程,会完成这个程序需要的业务操作。

守护线程:是为其他服务的线程,在后台默默地进行完成一些系统的服务。守护线程作为服务线程,没有服务对象就没有必要继续运行了。如果用户线程都全部结束了,说明程序完成了业务操作,系统可以退出了,如果此时只剩下守护线程时,java虚拟机会自动退出。

通过Thread.currentThread().isDaemon()来判断是否是守护线程

注意:thread.setDaemon(true)必须在thread.start()之前进行设置,否则会有IllegalThreadStateException异常。因为不能将正在运行的用户线程设置为守护线程。

二、Future

2.1 Future接口

Future接口((FutureTask实现类)定义了操作异步任务执行的一些方法,如获取异步任务的执行结果,取消任务的执行,判断任务是否被取消,判断任务执行是否完毕。

Future接口可以为主线程开启一个分支任务,专门处理主线程耗时和费力的复杂任务。

2.2 Future接口实现类FutureTask异步任务

FutureTask 类实现 RunnableFuture接口,而RunnableFuture接口 继承 Runnable和Future接口

FutureTask可以满足 多线程 / 有返回 /异步任务

2.3 FutureTask类 使用 示例

以FutureTask类使用Callable接口返回String类型的值为例

public class CompletableFutureDemo {

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

        FutureTask<String> futureTask = new FutureTask<>(new MyThread());
        Thread t1 = new Thread(futureTask,"t1");

        t1.start();
        System.out.println(futureTask.get());


    }
}

class MyThread implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println(".......come is call() ");
        return "Hello Callable";
    }
}

2.4 Future的优缺点分析:

优点:future+线程池异步多线程任务配合,能显著提高程序的执行效率

示例:使用一个线程执行3个任务:

        long startTime = System.currentTimeMillis();

        try{
            TimeUnit.MICROSECONDS.sleep(500);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        try{
            TimeUnit.MICROSECONDS.sleep(300);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        try{
            TimeUnit.MICROSECONDS.sleep(300);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("consumeTime:" + (endTime - startTime) + "毫秒");
        System.out.println(Thread.currentThread().getName()+"\t ------end");

示例:使用开启异步任务来处理三个任务

        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        long startTime = System.currentTimeMillis();
        FutureTask<String> futureTask1 = new FutureTask<String>(()->{
            try{
                TimeUnit.MICROSECONDS.sleep(500);
            }catch (InterruptedException e){
                e.printStackTrace();
            }

            return "task1 over";
        });
        threadPool.submit(futureTask1);

        FutureTask<String> futureTask2 = new FutureTask<String>(()->{
            try{
                TimeUnit.MICROSECONDS.sleep(300);
            }catch (InterruptedException e){
                e.printStackTrace();
            }

            return "task2 over";
        });

        threadPool.submit(futureTask2);


        FutureTask<String> futureTask3 = new FutureTask<String>(()->{
           try {
               TimeUnit.MICROSECONDS.sleep(300);
           }catch (InterruptedException e){
               e.printStackTrace();
           }

           return "task3 over";
        });

        threadPool.submit(futureTask3);


        long endTime = System.currentTimeMillis();
        System.out.println("consumeTime:" + (endTime - startTime) + "毫秒");
        System.out.println(Thread.currentThread().getName()+"\t ------end");

从运行的结果来看,开启多个异步任务线程来处理的耗时更低,如果获取 FutureTask中返回的数据会增加耗时   

缺点:

使用get()会阻塞线程

一旦使用get()方法获取数据,那么线程就会等待执行结束后获取返回的值才可以继续往下执行。

轮询耗费CPU

轮询的方式会消耗CPU资源,也不能及时的获取计算的结果,如果想获取异步的结果,通常使用轮询的方式取获取结果,尽量不阻塞。

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


        FutureTask<String> futureTask1 = new FutureTask<String>(()->{
            System.out.println(Thread.currentThread().getName()+"----------begin");
            try{
                TimeUnit.SECONDS.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }

            return "task1 over";
        });

        Thread t1 =new Thread(futureTask1,"t1");
        t1.start();


        while(true){
            if(futureTask1.isDone()){
                System.out.println(futureTask1.get());
                break;
            }else {

                try{
                    TimeUnit.SECONDS.sleep(2);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println("正在处理中,请稍后尝试。");

            }
        }
    }

2.5 Future异步优化思路

  • 想将多个异步任务的计算结果组合起来,后一个异步任务的计算结果需要前一个异步任务的值

就是将两个或多个异步计算合成一个异步计算,这几个异步计算互相独立,同时后面这个有依赖前一个处理的结果

  • 对计算速度选最快

Future集合中某个任务快结束时,返回结果,返回第一个处理的结果

三、CompletableFuture

3.1 CompletableFuture概述

CompletableFuture是对Future的改进

CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方

get()方法在Future计算完成之前处于阻塞状态

isDone()方法容易消耗CPU资源

对于异步处理希望可以通过传入回调函数,在Future结束时自动调用该回调函数,这样就无需等待结果。

CompletableFuture实现CompletionStage和Future接口

CompletionStage代表异步计算过程中的某个阶段,一个阶段完成后会触发另外一个将阶段

一个阶段的计算执行可以是一个Function,Consumer或者Runnable。

一个阶段的执行可能被单个阶段的完成触发,也可以有多个阶段一起触发。

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 executor参数:

  • 没有指定Executor方法,默认为Fork.JoinPoll.commonPool()作为它线程池执行异步代码。
  • 如果指定线程池,则使用指定的线程池执行异步代码
     

示例代码: runAsync和supplyAsync方法

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

        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
            System.out.println(Thread.currentThread().getName()+"------------------begin");
            try{
                TimeUnit.SECONDS.sleep(1);
            }catch (InterruptedException e){
                e.printStackTrace();
            }

        },threadPool);

        CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"------------------begin");
            try{
                TimeUnit.SECONDS.sleep(1);

            }catch (InterruptedException e){
                e.printStackTrace();
            }

            return "Hello CompletableFuture";
        },threadPool);

        System.out.println(completableFuture2.get());
        
        threadPool.shutdown();

    }

使用CompletableFuture中的supplyAsync()方法解决Future的get()方法造成的阻塞问题示例:

whenComplete中的v是supplyAsync()方法返回的result结果

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

        CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"------------------begin");
            int result = ThreadLocalRandom.current().nextInt(10);

            try{
                TimeUnit.SECONDS.sleep(1);
                System.out.println("1秒后获得结果:"+result);
                return  result;
            }catch (InterruptedException e){
                e.printStackTrace();
            }

            return "Hello CompletableFuture";
        }).whenComplete((v,e)->{
            if(e==null){
                System.out.println("----计算完成:"+v);
            }
        }).exceptionally(e->{
            e.printStackTrace();
            System.out.println("异常情况" + e.getCause() + "\t"+e.getMessage());
            return null;
        });

        System.out.println(Thread.currentThread().getName()+"线程忙其他的任务");

        //主线程不要立即结束,否则CompletableFuture默认的线程池会立刻关闭
        try{
            TimeUnit.SECONDS.sleep(3);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

使用线程池后:默认的线程池会自动关闭,而自定义的线程池需要使用shutdown()方法进行关闭

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

        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        try{
            CompletableFuture.supplyAsync(()->{
                System.out.println(Thread.currentThread().getName()+"------------------begin");
                int result = ThreadLocalRandom.current().nextInt(10);

                try{
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println("1秒后获得结果:"+result);
                    return  result;
                }catch (InterruptedException e){
                    e.printStackTrace();
                }

                return "Hello CompletableFuture";
            },threadPool).whenComplete((v,e)->{
                if(e==null){
                    System.out.println("----计算完成:"+v);
                }
            }).exceptionally(e->{
                e.printStackTrace();
                System.out.println("异常情况" + e.getCause() + "\t"+e.getMessage());
                return null;
            });
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadPool.shutdown();
        }

        System.out.println(Thread.currentThread().getName()+"线程忙其他的任务");
        
    }

 3.3 ComletableFuture的优点:

异步任务结束时,会自动回调某个对象的方法

主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行

异步任务出错时,会自动回调某个对象的方法

3.4 CompletableFuture链式语法和join方法介绍

函数式接口名称方法名称参数返回值
Runnablerun无参数无返回值
Functionapply1个参数有返回值
Consumeaccept1个参数无返回值
Supplierget无参数有返回值
Biconsumeraccept2个参数无返回值

链式语法在实体类上加上注解 @Accessors(chain = true)

这样对象就可以像链条一样赋值

public class Main {

    public static void main(String[] args){
        Student student = new Student();
        student.setId(1).setName("csh").setSchool("zk");
    }
    
}


@NoArgsConstructor
@AllArgsConstructor
@Data
@Accessors(chain = true)
class Student{
    private long id;
    private String name;
    private String school;
}

join方法和get方法差不多,但是join在编译时不会检查信息的异常

3.5 电商案例

一起搜索的效率比一家一家的搜索效率更高


/**
 *
 * 案例说明:电商比价需求,模拟如下情况:
 *
 * 1需求:
 *  1.1 同一款产品,同时搜索出同款产品在各大电商平台的售价;
 *  1.2 同一款产品,同时搜索出本产品在同一个电商平台下,各个入驻卖家售价是多少
 *
 * 2输出:出来结果希望是同款产品的在不同地方的价格清单列表,返回一个List<String>
 * 《mysql》 in jd price is 88.05
 * 《mysql》 in dangdang price is 86.11
 * 《mysql》 in taobao price is 90.43
 *
 * 3 技术要求
 *   3.1 函数式编程
 *   3.2 链式编程
 *   3.3 Stream流式计算
 */
public class CompletableFutureMallDemo
{
    static List<NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("dangdang"),
            new NetMall("taobao"),
            new NetMall("pdd"),
            new NetMall("tmall")
    );

    /**
     * step by step 一家家搜查
     * List<NetMall> ----->map------> List<String>
     * @param list
     * @param productName
     * @return
     */
    public static List<String> getPrice(List<NetMall> list,String productName)
    {
        //《mysql》 in taobao price is 90.43
        return list
                .stream()
                .map(netMall ->
                        String.format(productName + " in %s price is %.2f",
                                netMall.getNetMallName(),
                                netMall.calcPrice(productName)))
                .collect(Collectors.toList());
    }

    /**
     * List<NetMall> ----->List<CompletableFuture<String>>------> List<String>
     * @param list
     * @param productName
     * @return
     */
    public static List<String> getPriceByCompletableFuture(List<NetMall> list,String productName)
    {
        return list.stream().map(netMall ->
                CompletableFuture.supplyAsync(() -> String.format(productName + " in %s price is %.2f",
                netMall.getNetMallName(),
                netMall.calcPrice(productName))))
                .collect(Collectors.toList())
                .stream()
                .map(s -> s.join())
                .collect(Collectors.toList());
    }


    public static void main(String[] args)
    {
        long startTime = System.currentTimeMillis();
        List<String> list1 = getPrice(list, "mysql");
        for (String element : list1) {
            System.out.println(element);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒");

        System.out.println("--------------------");

        long startTime2 = System.currentTimeMillis();
        List<String> list2 = getPriceByCompletableFuture(list, "mysql");
        for (String element : list2) {
            System.out.println(element);
        }
        long endTime2 = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime2 - startTime2) +" 毫秒");
    }
}

class NetMall
{
    @Getter
    private String netMallName;

    public NetMall(String netMallName)
    {
        this.netMallName = netMallName;
    }

    public double calcPrice(String productName)
    {
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
    }
}

3.6 getNow()方法 和complete()方法

getNow():

通过设定预设结果

  • 如果计算完成,则getNow()获取返回的结果
  • 如果没有计算完成,则getNow()获取预设的结果

complete():

  • 如果 CompletableFuture 已经被完成(无论是通过正常完成还是异常),那么调用 complete() 方法将不会做任何事情,并且返回 false

  • 如果 CompletableFuture 还没有被完成,那么调用 complete() 方法将设置给定的结果值,并且返回 true

3.7 thenApply 和 handle 对计算结果进行处理

thenApply:

计算结果存在依赖,两个线程串行化

由于存在依赖关系,当步报错就不走下一步,当前步骤异常则停止。

handle:

有异常也会继续执行处理

handle示例代码:

    public static void main(String[] args)
    {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        CompletableFuture.supplyAsync(() ->{
            //暂停几秒钟线程
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("111");
            return 1;
        },threadPool).handle((f,e) -> {
            int i=10/0;
            System.out.println("222");
            return f + 2;
        }).handle((f,e) -> {
            System.out.println("333");
            return f + 3;
        }).whenComplete((v,e) -> {
            if (e == null) {
                System.out.println("----计算结果: "+v);
            }
        }).exceptionally(e -> {
            e.printStackTrace();
            System.out.println(e.getMessage());
            return null;
        });

        System.out.println(Thread.currentThread().getName()+"----主线程先去忙其它任务");

        threadPool.shutdown();
    }

3.8 对计算结果进行消费

thenAccept:接收任务的处理结果,并消费处理,无返回结果。

 CompletableFuture.supplyAsync(() -> {
            return 1;
        }).thenApply(f ->{
            return f + 2;
        }).thenApply(f ->{
            return f + 3;
        }).thenAccept(System.out::println);

 3.9 CompletableFuture和线程池

thenRun 和 thenRunAsync 的区别

  • 线程执行:thenRun 使用同一个线程(调用者的线程)来执行 Runnable,而 thenRunAsync 使用一个新的线程来执行 Runnable
  • 性能开销:由于 thenRunAsync 需要创建新线程,所以它可能会引入额外的性能开销,特别是当任务非常轻量级时。
  • 线程池thenRunAsync 允许你指定一个自定义的线程池来执行 Runnable,而 thenRun 不允许这样做。

选择使用 thenRun 还是 thenRunAsync 取决于你的具体需求。如果你不需要在单独的线程上执行任务,或者你想避免创建新线程的开销,那么 thenRun 是更好的选择。如果你需要在不同的线程上执行任务,或者想利用并行计算来提高性能,那么 thenRunAsync 是更好的选择。

3.10 applyToEither  对计算速度进行选用

CompletableFuture 提供了一个名为 applyToEither 的方法,它与Scala中的 applyToEither 功能类似。applyToEither 方法允许你在一个 CompletableFuture 和另一个 CompletableFuture 中的任意一个完成时,使用其结果来执行一个函数。执行的是最快返回结果的那个

 

    public static void main(String[] args)
    {
        CompletableFuture<String> playA = CompletableFuture.supplyAsync(() -> {
            System.out.println("A come in");
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            return "playA";
        });

        CompletableFuture<String> playB = CompletableFuture.supplyAsync(() -> {
            System.out.println("B come in");
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            return "playB";
        });

        CompletableFuture<String> result = playA.applyToEither(playB, f -> {
            return f + " is winer";
        });

        System.out.println(Thread.currentThread().getName()+"\t"+"-----: "+result.join());
    }

3.11 thenCombine 对计算结果进行合并

两个CompletionStage任务完成后,将两个任务的结果一起交给thenCombine来处理

public static void main(String[] args)
    {
        CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t ---启动");
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 10;
        });

        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t ---启动");
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 20;
        });

        CompletableFuture<Integer> result = completableFuture1.thenCombine(completableFuture2, (x, y) -> {
            System.out.println("-----开始两个结果合并");
            return x + y;
        });

        System.out.println(result.join());

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

探索星辰大海

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值