JUC高级篇-第1章 CompletableFutrue

1.Future接口理论

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

Callable接口中定义了需要有返回的任务需要实现的方法。

比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,忙其它事情或者先执行完,过了一会才去获取子任务的执行结果或变更的任务状态。

2.FutureTask异步任务

Future接口能干什么

Future是Java5新加的一个接口,它提供了一种异步并行计算的功能。

如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中执行。主线程继续处理其他任务或者先行结束,再通过Future获取计算结果。

Runnable接口和Callable接口
Future接口和FutureTask实现类

目的:异步多线程任务执行且返回有结果,三个特点:多线程/有返回/异步任务(班长为老师去买水作为新启动的异步多线程任务且买到水有结果返回)

本源的Future接口相关架构

Future编码实战和优缺点分析

public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(new MyThread());
        new Thread(futureTask).start();
        String s = futureTask.get();
        System.out.println(s);
    }
}

class MyThread implements Callable<String>{

    @Override
    public String call() throws Exception {
        return "hello callable";
    }
}

Future接口优点:future结合线程池执行异步多线程任务配合能显著提高程序的执行效率。

Future接口缺点:1)get容易导致阻塞一般建议放在程序后面,get中可以传递过时时间。2)isDone轮询导致CPU空转。如果想要异步获取结果,通常都会以轮询的方式去获取结果 尽量不要阻塞。

异步优化思路

回调通知

创建异步任务

多个任务前后依赖可以组合处理

对计算速度选最快

3.CompletableFuture的改进

CompletableFuture为什么出现

get()方法在Future计算完成之前会一直处在阻塞状态下,isDone方法容易耗费CPU资源,对于真正的异步处理我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果。

阻塞的方式和异步编程的设计理念相违背,而轮询的方式会耗费无谓的CPU资源。因此,JDK8设计出CompletableFuture。

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

CompletableFuture和CompletionStage源码分别介绍

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

CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段,有些类似Linux系统的管道分隔符传参数。

一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发

一个阶段的计算执行可以是一个Function,Consumer或者Runnable。比如:state.thenApply(x->square(x)).thenAccept(x->System.out.print(x)).thenRun(()->System.out.println())

CompletableFuture:

在Java8中,CompletableFuture提供了非常强大的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合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的方法,直接使用默认的ForkJoinPool.commonPool() 作为它的线程池执行异步代码。
  • 如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码
public class CompletableFutureDemo2
{
    public static void main(String[] args) throws ExecutionException, InterruptedException
    {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName()+"\t"+"-----come in");
            //暂停几秒钟线程
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("-----task is over");
        });
        System.out.println(future.get());
    }
}
public class CompletableFutureDemo2
{
    public static void main(String[] args) throws ExecutionException, InterruptedException
    {
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t" + "-----come in");
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return ThreadLocalRandom.current().nextInt(100);
        });

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

从Java8开始引入了CompletableFuture,它是Future的功能增强版,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法

public class cfuture4
{
    public static void main(String[] args) throws Exception
    {
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t" + "-----come in");
            int result = ThreadLocalRandom.current().nextInt(10);
            //暂停几秒钟线程
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("-----计算结束耗时1秒钟,result: "+result);
            if(result > 6)
            {
                int age = 10/0;
            }
            return result;
        }).whenComplete((v,e) ->{
            if(e == null)
            {
                System.out.println("-----result: "+v);
            }
        }).exceptionally(e -> {
            System.out.println("-----exception: "+e.getCause()+"\t"+e.getMessage());
            return -44;
        });

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

CompletableFuture的优点

异步任务结束时,会自动回调某个对象的方法;
异步任务出错时,会自动回调某个对象的方法;
主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行

4.案例

Lambda +Stream+链式调用+Java8函数式编程带走

Runnable Function Consumer Supplier BiConsumer

案例要求:

经常出现在等待某条 SQL 执行完成后,再继续执行下一条 SQL ,而这两条 SQL 本身是并无关系的,可以同时进行执行的。

我们希望能够两条 SQL 同时进行处理,而不是等待其中的某一条 SQL 完成后,再继续下一条。同理,
对于分布式微服务的调用,按照实际业务,如果是无关联step by step的业务,可以尝试是否可以多箭齐发,同时调用。

我们去比同一个商品在各个平台上的价格,要求获得一个清单列表,
1 step by step,查完京东查淘宝,查完淘宝查天猫......

2 all 一口气同时查询。。。。。

ublic class T1
{
    static List<NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("tmall"),
            new NetMall("pdd"),
            new NetMall("mi")
    );

    public static List<String> findPriceSync(List<NetMall> list,String productName)
    {
        return list.stream().map(mall -> String.format(productName+" %s price is %.2f",mall.getNetMallName(),mall.getPriceByName(productName))).collect(Collectors.toList());
    }

    public static List<String> findPriceASync(List<NetMall> list,String productName)
    {
        return list.stream().map(mall -> CompletableFuture.supplyAsync(() -> String.format(productName + " %s price is %.2f", mall.getNetMallName(), mall.getPriceByName(productName)))).collect(Collectors.toList()).stream().map(CompletableFuture::join).collect(Collectors.toList());
    }


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

        long startTime2 = System.currentTimeMillis();
        List<String> list2 = findPriceASync(list, "thinking in java");
        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 getPriceByName(String productName)
    {
        return calcPrice(productName);
    }

    private double calcPrice(String productName)
    {
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        return ThreadLocalRandom.current().nextDouble() + productName.charAt(0);
    }
}
 

get与join的区别:

作用几乎一样,join可以允许用户不去捕捉异常

5.CompletableFuture常用方法

1.获得结果和触发计算

获取结果

public T get()//不见不散
public T get(long timeout, TimeUnit unit)//过时不候
public T join()//不抛异常
public T getNow(T valueIfAbsent)//立即获取结果不阻塞-没有计算完成的情况下,给我一个替代结果

主动触发计算

public boolean complete(T value)//是否打断get方法立即返回括号值

2.对结果进行处理

thenApply//计算结果存在依赖关系,这两个线程串行化  由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停
handle//计算结果存在依赖关系,这两个线程串行化  有异常也可以往下一步走,根据带的异常参数可以进一步处理

3.对计算结果进行消费

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

Code之任务之间的顺序执行
thenRun(Runnable runnable) 任务 A 执行完执行 B,并且 B 不需要 A 的结果
thenAccept(Consumer action) 任务 A 执行完执行 B,B 需要 A 的结果,但是任务 B 无返回值
thenApply(Function fn) 任务 A 执行完执行 B,B 需要 A 的结果,同时任务 B 有返回值

关于线程池:

1没有传入自定义线程池,都用默认线程池ForkJoinPool;

2传入了一个自定义线程池,如果你执行第一个任务的时候,传入了一个自定义线程池:

  • 调用thenRun方法执行第二个任务时,则第二个任务和第一个任务是共用同一个线程池。

  • 调用thenRunAsync执行第二个任务时,则第一个任务使用的是你自己传入的线程池,第二个任务使用的是ForkJoin线程池

3备注

有可能处理太快,系统优化切换原则,直接使用main线程处理中,其它如: thenAccept和thenAcceptAsync,thenApply和thenApplyAsync等,它们之间的区别也是同理

4.对计算速度进行选用

applyToEither//谁快用谁

5.对计算结果进行合并

thenCombine
//两个CompletionStage任务都完成后,最终能把两个任务的结果一起交给thenCombine 来处理
//先完成的先等着,等待其它分支任务

posted @ 2022-11-17 22:20  帅气的小峰  阅读(48)  评论(0编辑  收藏  举报