CompletableFuture

CompletableFuture

Future接口理论知识复习

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

比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,忙完其他事情或者先执行完,过了一会再才去获取子任务的执行结果或变更的任务状态(举例:老师上课时间想喝水,他继续讲课不结束上课这个主线程,让学生去小卖部帮老师买水完成这个耗时和费力的任务)。

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


Future接口常用实现类FutureTask异步任务

Future接口能干什么

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

Future接口相关架构

  • 目的:异步多线程任务执行且返回有结果,三个特点:多线程、有返回、异步任务(班长为老师去买水作为新启动的异步多线程任务且买到水有结果返回)
  • 代码实现:Runnable接口+Callable接口+Future接口和FutureTask实现类

/**
 * @author : Cammy.Wu
 * Description : FutureTask开启异步任务
 */

public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(new MyThread());

        Thread t = new Thread(futureTask, "t");
        t.start();

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


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

Future编码实战和优缺点分析

  • 优点:Future+线程池异步多线程任务配合,能显著提高程序的运行效率。
/**
 * @author : Cammy.Wu
 * Description : Future+线程池异步多线程任务配合
 */

public class FutureThreadPoolDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 3个任务,目前开启多个异步任务线程来处理,请问耗时多少?

        ExecutorService threadPool= Executors.newFixedThreadPool(3);

        long startTime = System.currentTimeMillis();

        FutureTask<String> task1 = new FutureTask<String>(()->{
            try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
            return "task1 over";
        });
        threadPool.submit(task1);
        FutureTask<String> task2 = new FutureTask<String>(()->{
            try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
            return "task2 over";
        });
        threadPool.submit(task2);

        System.out.println(task1.get());
        System.out.println(task2.get());

        FutureTask<String> task3 = new FutureTask<String>(()->{
            try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
            return "task3 over";
        });
        threadPool.submit(task3);

        long endTime = System.currentTimeMillis();
        System.out.println("耗时:"+(endTime-startTime));
        System.out.println(Thread.currentThread().getName() );
    }

    private static void m1() {
        // 3个任务,目前只有一个线程main来处理,请问耗时多少?

        long startTime = System.currentTimeMillis();

        // 暂停毫秒
        try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
        try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }

        long endTime = System.currentTimeMillis();
        System.out.println("耗时:"+(endTime-startTime));

        System.out.println(Thread.currentThread().getName() );
    }
}
  • 缺点
    • get()阻塞 — 一旦调用get()方法求结果,一旦调用不见不散,非要等到结果才会离开,不管你是否计算完成,如果没有计算完成容易程序堵塞。
    • isDone()轮询 — 轮询的方式会耗费无谓的cpu资源,而且也不见得能及时得到计算结果,如果想要异步获取结果,通常会以轮询的方式去获取结果,尽量不要阻塞。
/**
 * @author : Cammy.Wu
 * Description : Future获取结果get()和轮询
 */

public class FutureAPIDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        FutureTask<String> futureTask = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() + "\t ------ come in");
            // 暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "task over";
        });

        Thread thread = new Thread(futureTask, "T1");
        thread.start();

        System.out.println(Thread.currentThread().getName()+"忙其他的任务去了");
        // System.out.println(futureTask.get()); // 一旦调用get(),线程就会阻塞,直到获取到结果为止
        // System.out.println(futureTask.get(3,TimeUnit.SECONDS)); // 不优雅

        while (true){
            if (futureTask.isDone()) {
                System.out.println(futureTask.get());
                break;
            }else{
                // 暂停毫秒
                try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println("正在处理中,不要再催啦。。。");
            }
        }
    }

    /*
        1.get容易导致阻塞,一般建议放在程序后面,一旦调用就会不见不散,非要等到结果才会离开,不管你是否计算完成,容易引起阻塞。
        2.假如我不愿意等待很长时间,我希望过时不候,可以自动离开
     */
}
  • 结论:Future对于结果的获取不是很友好,只能通过阻塞或轮询的方式得到任务的结果。

完成一些复杂的任务

  • 对于简单的业务场景使用Future完全ok
  • 回调通知:
    • 应对Future的完成时间,完成了可以告诉我,也就是我们的回调通知
    • 通过轮询的方式去判断任务是否完成这样非常占cpu并且代码也不优雅
  • 创建异步任务:Future+线程池组合
  • 多个任务前后依赖可以组合处理(水煮鱼—>买鱼—>调料—>下锅):
    • 想将多个异步任务的结果组合起来,后一个异步任务的计算结果需要钱一个异步任务的值
    • 想将两个或多个异步计算合并成为一个异步计算,这几个异步计算互相独立,同时后面这个又依赖前一个处理的结果
  • 对计算速度选最快的:
    • 当Future集合中某个任务最快结束时,返回结果,返回第一名处理结果
  • 结论
    • 使用Future之前提供的那点API就囊中羞涩,处理起来不够优雅,这时候还是让CompletableFuture以声明式的方式优雅的处理这些需求。
    • 从i到i++
    • Future能干的,CompletableFuture都能干

CompletableFuture对Future的改进

CompletableFuture为什么会出现

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

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

CompletableFuture和CompletionStage介绍

类架构说明:

  • 接口CompletionStage
    • 代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段。
    • 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发
  • 类CompletableFuture
    • 提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法
    • 它可能代表一个明确完成的Future,也可能代表一个完成阶段(CompletionStage),它支持在计算完成以后触发一些函数或执行某些动作

核心的四个静态方法,来创建一个异步任务

四个静态构造方法

对于上述Executor参数说明:若没有指定,则使用默认的ForkJoinPoolcommonPool()作为它的线程池执行异步代码,如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码

/**
 * @author : Cammy.Wu
 * Description : 四个静态方法演示
 */

public class CompletableFutureBuildDemo {

    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());
            // 暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, threadPool);

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

        CompletableFuture<String> objectCompletableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello supplyAsync";
        }, threadPool);

        System.out.println(objectCompletableFuture.get());

        threadPool.shutdown();
    }
}

CompletableFuture减少阻塞和轮询,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。

/**
 * @author : Cammy.Wu
 * Description : CompletableFuture使用演示
 */

public class CompletableFutureUseDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "---come in");
            // 生成一个随机整数作为结果
            int result = ThreadLocalRandom.current().nextInt(10);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (result > 5) { // 模拟产生异常情况
                int i = 10 / 0;
            }
            System.out.println("---1秒钟后出结果:" + result);
            return result;
        },threadPool).whenComplete((v,e)->{
            if (e == null) {
                System.out.println("----计算完成,更新结果" + v);
            }
        }).exceptionally(e -> {
            e.printStackTrace();
            System.out.println("异常情况:"+e.getCause()+e.getMessage());
            return null;
        });
        System.out.println(Thread.currentThread().getName()+"----主线程先去忙其它任务");
        threadPool.shutdown();
    }
}

CompletableFuture优点:

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

案例精讲-从电商网站的比价需求展开

函数式编程已成为主流

Lambda表达式+Stream流式调用+Chain链式调用+Java8函数式编程

函数式接口:

  • Runnable:无参数、无返回值
@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
  • Function:接受一个参数,并且有返回值
@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

    ...
}
  • Consumer:接受一个参数,没有返回值
@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    ...
}
  • BiConsumer:接受两个参数,没有返回值
@FunctionalInterface
public interface BiConsumer<T, U> {

    /**
     * Performs this operation on the given arguments.
     *
     * @param t the first input argument
     * @param u the second input argument
     */
    void accept(T t, U u);

    ...
}
  • Supplier:没有参数,有返回值
@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

chain链式调用(链式编程)

/**
 * @author : Cammy.Wu
 * Description : Chain链式调用演示
 */

public class CompletableFutureChainDemo {
    public static void main(String[] args) {
        Student student = new Student();
        student.setId(1).setName("Cammy").setMajor("computer"); // 链式调用
        System.out.println(student);
    }
}



@AllArgsConstructor // 全参数构造器注解,用于生成一个包含类中所有属性的构造器
@NoArgsConstructor // 无参构造器注解,用于生成一个不包含任何参数的构造器
@Data
@Accessors(chain = true) // 开启链式调用
class Student{
    private Integer id;
    private String name;
    private String major;
}

大厂业务需求说明

切记:功能—>性能(完成—>完美)

电商网站比价需求分析:

  1. 需求说明:
    1. 同一款产品,同时搜索出同款产品在各大电商平台的售价
    2. 同一款产品,同时搜索出本产品在同一个电商平台下,各个入驻卖家售价是多少
  2. 输出返回:
    1. 出来结果希望是同款产品的在不同地方的价格清单列表,返回一个List

例如:《Mysql》 in jd price is 88.05 《Mysql》 in taobao price is 90.43

  1. 解决方案,对比同一个产品在各个平台上的价格,要求获得一个清单列表
    1. step by step,按部就班,查完淘宝查京东,查完京东查天猫…
    2. all in,万箭齐发,一口气多线程异步任务同时查询

一波流Java8函数式编程带走-比价案例实战Case

/**
 *
 * 案例说明:电商比价需求,模拟如下情况:
 *
 * 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")
    );

    // step by step 一家家搜
    public static List<String> getPrice(List<NetMall> list, String productName) {
        return list.stream()
                .map(netMall -> String.format(productName + "in %s price %.2f",
                        netMall.getMallName(), netMall.calcPrice(productName)))
                .collect(Collectors.toList());
    }

    // List<NetMall> ---> List<CompletableFuture<String>> ---> List<String>
    public static List<String> getPriceByCompletableFuture(List<NetMall> list, String productName) {
        return list.stream().map(netMall -> CompletableFuture.supplyAsync(() -> String.format(productName + "in %s price %.2f",
                        netMall.getMallName(),
                        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> mysql = getPrice(list, "mysql");
        List<String> mysql = getPriceByCompletableFuture(list, "mysql");
        for (String element : mysql) {
            System.out.println(element);
        }
        long endTime = System.currentTimeMillis();

        System.out.println("cost time : " + (endTime - startTime));
    }
}


@NoArgsConstructor
@AllArgsConstructor
@Data
class NetMall {
    private String mallName;

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

}

CompletableFuture常用方法

  • 获得结果和触发计算
    • 获取结果
      • public T get()
      • public T get(long timeout,TimeUnit unit)
      • public T join() —>和get一样的作用,只是不需要抛出异常
      • public T getNow(T valuelfAbsent) —>计算完成就返回正常值,否则返回备胎值(传入的参数),立即获取结果不阻塞
    • 主动触发计算
      • public boolean complete(T value) ---->是否打断get方法立即返回括号值
/**
 * @author : Cammy.Wu
 * Description : 对计算结果进行处理演示
 */

public class CompletableFutureAPIDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            // 暂停几秒线程
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "abc";
        });

        // System.out.println(completableFuture.get());
        // System.out.println(completableFuture.get(2L, TimeUnit.SECONDS));
        // System.out.println(completableFuture.join());
        // 暂停几秒线程
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // System.out.println(completableFuture.getNow("hello world"));

        System.out.println(completableFuture.complete("completeValue")+"\t"+completableFuture.join());
    }
}
  • 对计算结果进行处理
    • thenApply —>计算结果存在依赖关系,这两个线程串行化---->由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停
    • handle —>计算结果存在依赖关系,这两个线程串行化---->有异常也可以往下走一步
/**
 * @author : Cammy.Wu
 * Description : 对计算结果进行处理
 */

public class CompletableFutureAPI2Demo
{
    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;
        }).thenApply((f) -> {
            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();
    }
}
  • 对计算结果进行消费
    • 接受任务的处理结果,并消费处理,无返回结果
    • thenAccept
/**
 * @author : Cammy.Wu
 * Description : 对计算结果进行消费
 */

public class CompletableFutureAPI3Demo
{
    public static void main(String[] args)
    {
        CompletableFuture.supplyAsync(() -> {
            return 1;
        }).thenApply(f ->{
            return f + 2;
        }).thenApply(f ->{
            return f + 3;
        }).thenAccept(System.out::println);
    }
}
- 对比补充
    * thenRun(Runnable runnable) :任务A执行完执行B,并且不需要A的结果
    * thenAccept(Consumer action): 任务A执行完执行B,B需要A的结果,但是任务B<font style="color:#DF2A3F;">没有返回值</font>
    * thenApply(Function fn): 任务A执行完执行B,B需要A的结果,同时任务B<font style="color:#DF2A3F;">有返回值</font>
/**
 * @author : Cammy.Wu
 * Description : 对计算结果进行消费
 */

public class CompletableFutureAPI3Demo
{
    public static void main(String[] args)
    {
        // 任务A执行完执行B,并且不需要A的结果
        System.out.println(CompletableFuture.supplyAsync(() -> "resultA ").thenRun(() -> {}).join());
        System.out.println("-------------------------");
        // 任务A执行完执行B,B需要A的结果,但是任务B没有返回值
        System.out.println(CompletableFuture.supplyAsync(() -> "resultA ").thenAccept(r -> System.out.println(r)).join());
        System.out.println("-------------------------");
        // 任务A执行完执行B,B需要A的结果,同时任务B有返回值
        System.out.println(CompletableFuture.supplyAsync(() -> "resultA ").thenApply(r -> r + "resultB").join());

    }
}
- CompletableFuture和线程池说明
    * 如果没有传入自定义线程池,都用默认线程池ForkJoinPool
    * 传入一个线程池,如果你执行第一个任务时,传入了一个自定义线程池
        + 调用thenRun方法执行第二个任务时,则第二个任务和第一个任务时共用同一个线程池
        + 调用thenRunAsync执行第二个任务时,则第一个任务使用的是你自定义的线程池,第二个任务使用的是ForkJoin线程池
    * 备注:可能是线程处理太快,系统优化切换原则, 直接使用main线程处理,thenAccept和thenAcceptAsync,thenApply和thenApplyAsync等,之间的区别同理。
  • 对计算速度进行选用
    • 谁快用谁
    • applyToEither
/**
 * @author : Cammy.Wu
 * Description : 对计算速度进行选用
 */

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

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

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

        System.out.println(Thread.currentThread().getName()+"-----"+result.join());
    }
}
  • 对计算结果进行合并
    • 两个CompletableStage任务都完成后,最终能把两个任务的结果一起交给thenCombine来处理
    • 先完成的先等着,等待其他分支任务
/**
 * @author : Cammy.Wu
 * Description : 对计算结果进行合并
 */

public class CompletableFutureAPI5Demo {
    public static void main(String[] args) {
        CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "启动");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 10;
        });

        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "启动");
            try {
                TimeUnit.SECONDS.sleep(1);
            } 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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值