并发编程-08 Future&CompletableFuture用法

Future&CompletableFuture使用详解

1、Future

1.1 Callable和Runnable的区别

  • Callable有返回值,Runnable无返回值
  • Callable可以抛出异常,Runnable无法抛出异常
@FunctionalInterface
public interface Runnable {
	//执行run方法,无返回值
    public abstract void run();
}

@FunctionalInterface
public interface Callable<V> {
    //计算出结果,或者在无法完成时抛出异常(有返回值V)
    V call() throws Exception;
}

1.2 Future

1.2.1 Future的方法
public interface Future<V> {
    //尝试中断线程任务,如果任务已完成该接口调用将返回false,执行中或未开始的任务中断或不在运行
    boolean cancel(boolean mayInterruptIfRunning);

    //任务完成前被取消则返回ture
    boolean isCancelled();

    //判断任务是否已完成
    boolean isDone();

    //阻塞等待任务结果
    V get() throws InterruptedException, ExecutionException;

	//阻塞等待任务结果,超时timeout则抛出异常
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
1.2.2 Future的常规用法

JDK源码中,Doug Lea提供了2种标准的Future用法

 interface ArchiveSearcher { String search(String target); }
 class App {
   ExecutorService executor = ...//线程池
   ArchiveSearcher searcher = ...//
   void showSearch(final String target) throws InterruptedException {
     Future<String> future = executor.submit(new Callable<String>() {
         @Override
         public String call() {
             return searcher.search(target);
         }});
     displayOtherThings(); // do other things while searching
     try {
       displayText(future.get()); // use future 阻塞获取即如果
     } catch (ExecutionException ex) { cleanup(); return; }
   }
 }

通过FutureTask创建任务future,交给线程池执行

  FutureTask<String> future =
   new FutureTask<String>(new Callable<String>() {
     public String call() {
       return searcher.search(target);
   }});
 executor.execute(future);

1.3 FutureTask

并发编程-08 Future&CompletableFuture用法_第1张图片

1.3.1 FutureTask的使用方法
  • FutureTask实现了Runnable和Future,既可以通过Runnable的方式执行,也可以通过Callable的方式获取结果。
  • FutureTask可以作为消费者和生产者的桥梁。由生产者提交任务,消费者等待future的结果(future.get())
public class FutureUseMyDemo {

    static ExecutorService executorService = Executors.newFixedThreadPool(3);
    public static void main(String[] args) {
        FutureTask futureTask1 = new FutureTask(new T1Task("飞刀"));
        FutureTask futureTask2 = new FutureTask(new T2Task());
        FutureTask futureTask3 = new FutureTask(new T3Task());
        executorService.submit(futureTask1);
        executorService.submit(futureTask2);
        executorService.submit(futureTask3);
        try {
            System.out.println(futureTask1.get());
            System.out.println(futureTask2.get());
            System.out.println(futureTask3.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
        executorService.shutdown();
    }
    static class T1Task implements Callable<String> {
        private String extraName;
        public T1Task(String plusName){
            this.extraName = plusName;
        }

        @Override
        public String call() throws Exception {
            TimeUnit.SECONDS.sleep(2);
            return "小李"+extraName;
        }
    }

    static class T2Task implements Callable<String>{
        @Override
        public String call() throws Exception {
            TimeUnit.SECONDS.sleep(3);
            return "张三";
        }
    }

    static class T3Task implements Callable<String>{
        @Override
        public String call() throws Exception {
            TimeUnit.SECONDS.sleep(5);
            return "王五";
        }
    }
}
1.3.2 FutureTask使用注意事项
  • 当 for 循环批量获取 Future#get()容易 block时,可以使用get(timeout)保证不产生死锁
  • Future 的生命周期不能后退
1.3.3 FutureTask的局限性
  • 并发执行多任务时,get()结果时线程只能等待
  • 无法对多个任务执行链式调用,A任务结束之后开始执行B任务
  • 无法组合多个任务,Future无法保证在运行多个任务全部执行结束后执行特定动作
  • 没有关于异常的处理方法

2、CompletionService

并发编程-08 Future&CompletableFuture用法_第2张图片

2.1 CompletionService的原理和优点

  • 内部通过阻塞队列+FutureTask,实现了任务先完成先获取结果,按顺序获取排序排序。
  • 内部有先进先出的阻塞队列,用于保存已经执行完成的Future
  • take()或poll()方法,拿到已完成的Future
  • Future#get()接口获取最终结果

2.2 应用场景

2.2.1 常用方式
public class CompletionServiceDemo {

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

        //创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);
        //创建CompletionService
        CompletionService<Integer> cs = new ExecutorCompletionService<>(executor);
        //异步向电商S1询价
        cs.submit(() -> getPriceByS1());
        //异步向电商S2询价
        cs.submit(() -> getPriceByS2());
        //异步向电商S3询价
        cs.submit(() -> getPriceByS3());
        //将询价结果异步保存到数据库
        for (int i = 0; i < 3; i++) {
            //从阻塞队列获取futureTask
            Integer r = cs.take().get();
            executor.execute(() -> save(r));
        }


        executor.shutdown();
    }

    private static void save(Integer r) {
        log.debug("保存询价结果:{}",r);
    }

    private static Integer getPriceByS1() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(5000);
        log.debug("电商S1询价信息1200");
        return 1200;
    }
    private static Integer getPriceByS2() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(8000);
        log.debug("电商S2询价信息1000");
        return 1000;
    }
    private static Integer getPriceByS3()  throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(3000);
        log.debug("电商S3询价信息800");
        return 800;
    }
}
2.2.2 Dubbo Forking集群模式,单个任务成功时就返回
public class CompletionServiceDemo2 {
    public static void main(String[] args){
        Instant now = Instant.now();
        Object address = geocoder("address");
        System.out.println(Duration.between(now, Instant.now()));
    }

    @SneakyThrows
    public static Object geocoder(String addr){
        //创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);
        //创建CompletionService
        CompletionService<Boolean> cs = new ExecutorCompletionService<>(executor);
        //并行执行3个查询任务,当有1个任务获取结果后,则return
        cs.submit(() -> geocoderByS1(addr));
        cs.submit(() -> geocoderByS2(addr));
        cs.submit(() -> geocoderByS3(addr));
        Instant now = Instant.now();
/*        for (int i = 0; i < 3; i++) {
			//此处可以看到take()方法也是阻塞式的
            System.out.println(cs.take().get());
            System.out.println(Duration.between(now, Instant.now()));
        }*/
        return cs.take().get();
    }
    @SneakyThrows
    public static Boolean geocoderByS1(String addr) {
        TimeUnit.SECONDS.sleep(5);
        return true;
    }

    @SneakyThrows
    public static Boolean geocoderByS2(String addr) {
        TimeUnit.SECONDS.sleep(8);
        return true;
    }

    @SneakyThrows
    public static Boolean geocoderByS3(String addr){
        TimeUnit.SECONDS.sleep(2);
        return true;
    }
}

#PrintResult 
 PT2.054S
  • 批量提交异步任务时,可以使用CompletionService

  • CompletionService将线程池Executor和阻塞队列BlockingQueue的功能融合在了一

    起,能够让批量异步任务的管理更简单

  • CompletionService能够让异步任务的执行结果有序化。先执行完的先进入阻塞队列。

  • 线程池隔离。CompletionService支持自己创建线程池,这种隔离性能避免几个特别耗时的任务拖垮整个应用的风险。

3、CompletableFuture

3.1 CompletableFuture特点

  • CompletableFuture支持业务逻辑处理存在串行[依赖]、并行、聚合的关系
  • CompletableFuture是Future接口的扩展和增强
  • CompletableFuture实现了对任务的编排能力
  • 默认线程池:ForkJoinPool.commonPool()

3.1 应用场景

  • 描述依赖关系:
    • thenApply() 把前面异步任务的结果,交给后面的Function
    • thenCompose()用来连接两个有依赖关系的任务,结果由第二个任务返回
  • 描述and聚合关系:
    • thenCombine:任务合并,有返回值
    • thenAccepetBoth:两个任务执行完成后,将结果交给thenAccepetBoth消耗,无返回值
    • runAfterBoth:两个任务都执行完成后,执行下一步操作(Runnable)。
  • 描述or聚合关系:
    • applyToEither:两个任务谁执行的快,就使用那一个结果,有返回值。
    • acceptEither: 两个任务谁执行的快,就消耗那一个结果,无返回值。
    • runAfterEither: 任意一个任务执行完成,进行下一步操作(Runnable)。
  • 并行执行:
    • CompletableFuture类自己也提供了anyOf()和allOf()用于支持多个CompletableFuture并行执行

3.2 常用方法总结

  • runAsync 方法以Runnable函数式接口类型为参数,没有返回结果,
  • supplyAsync 方法Supplier函数式接口类型为参数,返回结果类型为U;Supplier 接口的 get() 方法是有返回值的(会阻塞
  • join()和get()方法都是用来获取CompletableFuture异步之后的返回值,join()方法抛出的是uncheck异常,get()方法抛出的是经过检查的异常,ExecutionException, InterruptedException 需要用户手动处理

3.3 使用案例

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

    Runnable runnable = () -> System.out.println("执行无返回结果的异步任务");
    CompletableFuture.runAsync(runnable);

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        System.out.println("执行有返回值的异步任务");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello World";
    });
    String result = future.join();
    System.out.println(result);
}
pletableFuture.supplyAsync(() -> {
        System.out.println("执行有返回值的异步任务");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello World";
    });
    String result = future.join();
    System.out.println(result);
}

你可能感兴趣的:(并发编程,java,后端,高并发编程)