【JUC】CompletableFuture对Future的改进

【JUC】CompletableFuture对Future的改进

1. Future弊端

  1. 阻塞
  2. cpu空转

1)想要得到Future任务的结果,使用 get() 方法阻塞线程直到结果返回。

FutureTask<String> futureTask = new FutureTask<String>(() -> {
    
    
    System.out.println(Thread.currentThread().getName() + "\t -----come in");
    //暂停几秒钟线程
    try {
    
    
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    return "task over";
});
Thread t1 = new Thread(futureTask, "t1");
t1.start();

System.out.println(Thread.currentThread().getName() + "\t ----忙其它任务了");
System.out.println(futureTask.get());

2)不想阻塞那就采用 isDone()轮询,轮询的方式会耗费无谓的cpu资源,而且也不见得能够及时获得结果。

FutureTask<String> futureTask = new FutureTask<String>(() -> {
    
    
    System.out.println(Thread.currentThread().getName() + "\t -----come in");
    //暂停几秒钟线程
    try {
    
    
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    return "task over";
});
Thread t1 = new Thread(futureTask, "t1");
t1.start();

System.out.println(Thread.currentThread().getName() + "\t ----忙其它任务了");

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

这两种方法都违背了“异步”思想。所以我们需要一个更加强大的类去实现方法的“异步”执行。于是就有了 CompletableFuture


2. CompletableFuture

在java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。

它可能代表一个明确完成的 Future,也有可能代表一个完成阶段(CompletionStage) ,它支持在计算完成以后触发一些函数或执行某些动作。

它还实现了Future和CompletionStage接口。


2.1 核心方法

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

  1. runAsync无返回值:public static CompletableFuture runAsync(Runnable runnable)
  2. runAsync无返回值:public static CompletableFuture runAsync(Runnable runnable,Executor executor)
  3. supplyAsync有返回值:public static CompletableFuture supplyAsync(Supplier supplier)
  4. supplyAsync有返回值:public static CompletableFuture supplyAsync(Supplier supplier,Executor executor)

如果指定了线程池则使用自己的线程池,没有的话默认使用系统提供的。

1)runAsync无返回值,不指定线程池

//方式1,没有返回值,并且不指定线程池
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
    
    
    System.out.println(Thread.currentThread().getName());
    try {
    
    
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
});
System.out.println(completableFuture.get());

2)runAsync无返回值,指定线程池

//方式2,有返回值,并且指定线程池
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());
//关闭线程池
threadPool.shutdown();

3)supplyAsync有返回值,不指定线程池

//方式3,有返回值,不指定线程池
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
    
    
    System.out.println(Thread.currentThread().getName());
    try {
    
    
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    return "hello world";
});
System.out.println(completableFuture.get());

4)supplyAsync有返回值,指定线程池

//方式4,有返回值,指定线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
    
    
    System.out.println(Thread.currentThread().getName());
    try {
    
    
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    return "hello world";
},threadPool);
System.out.println(completableFuture.get());
//关闭线程池
threadPool.shutdown();

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


2.2 优点

CompletableFuture有很多优点,比如:

  1. 异步任务结束时,会自动回调某个对象的方法。
  2. 异步任务出错时,会自动回调某个对象的方法。
  3. 主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行。
  public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        //future1();

        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        try {
    
    
            CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
    
    
                //异步任务。。。
                log.info("当前线程名:{}", Thread.currentThread().getName());
                //int i=1/0;
                int result = ThreadLocalRandom.current().nextInt(10);
                log.info("----3s后出结果:{}", result);
                try {
    
    
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                return result;
            }, threadPool).whenComplete((v, e) -> {
    
    
                //回调方法
                if (e == null) {
    
    
                    log.info("计算完成,更新updateValue值为:{}", v);
                }
            }).exceptionally(e -> {
    
    
                //发生错误时的回调方法
                e.printStackTrace();
                log.info("异常情况:{},\\t{}", e.getCause(), e.getMessage());
                return null;
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            threadPool.shutdown();
        }

        log.info("{}线程先去忙别的任务了", Thread.currentThread().getName());
    }

这种写法和js的请求方法回调类似。在 whenComplete 中执行任务正常完成后的操作,在 exceptionally 中执行任务发生异常后的操作。


2.3 get()和join()的区别

在Java的CompletableFuture类中,get()和join()也都是用于获取Future的执行结果的方法。它们的主要区别在于返回值和异常处理上。

join()方法会等待当前CompletableFuture对象的执行结果,如果执行过程中出现了异常,则会抛出Unchecked Exception。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    
    
    //do something and return result
    return result;
});

String result = future.join(); //等待future执行完毕,获取结果

//如果future执行过程中出现了异常,则会抛出Unchecked Exception

get()方法也会等待当前CompletableFuture对象的执行结果,但不同的是,如果执行过程中出现了异常,则会抛出Checked Exception(ExecutionException),这需要进行捕获或者声明抛出。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    
    
    //do something and return result
    return result;
});

try {
    
    
    String result = future.get(); //等待future执行完毕,获取结果
} catch (InterruptedException e) {
    
    
    //处理InterruptedException异常
} catch (ExecutionException e) {
    
    
    //处理ExecutionException异常
}

如果你不想在代码中显式地处理异常,并且可以确定CompletableFuture执行过程中不会出现异常,那么可以使用join()方法。否则,如果你需要对执行过程中出现的异常进行处理,可以使用get()方法。


3. CompletableFuture常用方法

3.1 获得结果和触发计算

获取结果:

public T getNow(T valueIfAbsent):立刻返回结果,没有计算完成的情况下给一个替代的结果(valueIfAbsent)。

主动触发计算:

public boolean complete(T value):没有计算完成则打断任务并将结果设置为value。


3.2 对计算结果进行处理

3.2.1 thenApply(Function<? super T,? extends U> fn)

thenApply:计算结果存在依赖关系,线程之间串行化,由于存在依赖关系,如果当前计算过程出错,则不会走到下一过程计算。

方法如下:

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) {
    
    
    return uniApplyStage(null, fn);
}

示例:

@Test
public void test() {
    
    
    long start = System.currentTimeMillis();
    //当一个线程依赖另一个线程时用 thenApply 方法来把这两个线程串行化,
    CompletableFuture.supplyAsync(() -> {
    
    
        //暂停几秒钟线程
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("111");
        return 1024;
    }).thenApply(f -> {
    
    
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("222");
        return f + 1;
    }).thenApply(f -> {
    
    
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        //int age = 10/0; // 异常情况:那步出错就停在那步。
        System.out.println("333");
        return f + 1;
    }).whenCompleteAsync((v, e) -> {
    
    
        System.out.println("*****v: " + v);
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - start)+"ms");
    }).exceptionally(e -> {
    
    
        e.printStackTrace();
        return null;
    });

    System.out.println("-----主线程结束,END");

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

运行结果:

image-20230402000438023


3.2.2 handle(BiFunction<? super T, Throwable, ? extends U> fn)

handle:计算结果存在依赖关系,线程之间串行化,当前计算过程有错则跳过,直接进入下一计算过程。

public <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) {
    
    
    return uniHandleStage(null, fn);
}

示例:

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();
}

运行结果:

image-20230402001130401


3.2.3 问题

whenComplete()whenCompleteAsync()的区别:

  1. whenComplete()方法是在当前线程中执行回调操作,即会阻塞当前线程直到回调操作执行完毕。如果异步任务已经完成,则立即执行回调操作;否则等待异步任务完成后再执行回调操作。
  2. whenComplete()方法是在当前线程中执行回调操作,即会阻塞当前线程直到回调操作执行完毕。如果异步任务已经完成,则立即执行回调操作;否则等待异步任务完成后再执行回调操作。
  3. 因此,whenComplete()方法会阻塞当前线程,而whenCompleteAsync()方法不会阻塞当前线程,会在新的线程中执行回调操作。

3.3 对计算结果进行消费

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

3.3.1 thenApply(Function<? super T,? extends U> fn)

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) {
    
    
    return uniApplyStage(null, fn);
}

示例:

@Test
public void test(){
    
    
    CompletableFuture.supplyAsync(() -> {
    
    
        return 1;
    }).thenApply(f -> {
    
    
        return f + 2;
    }).thenApply(f -> {
    
    
        return f + 3;
    }).thenApply(f -> {
    
    
        return f + 4;
    }).thenAccept(r -> System.out.println(r));
}

运行结果为:10


3.3.2 任务之间的顺序执行

thenRun(Runnable runnable):任务A执行完再执行B,并且B不需要A的结果

thenAccept(Consumer action):任务A执行完再执行B,B需要A的结果,B无返回值

thenApply(Function fn):任务A执行完再执行B,B需要A的结果,B有返回值

示例:

public static void main(String[] args){
    
    
    System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {
    
    }).join());
    System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenAccept(r -> System.out.println(r)).join());
    System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenApply(r -> r + "resultB").join());
}

运行结果如下:

image-20230402003206782


3.4 对计算速度进行选用

谁快就用谁

3.4.1 applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn)

方法如下:

public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn) {
    
    
    return orApplyStage(null, other, fn);
}

示例:

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());
}

运行结果如下:

image-20230402235436083


3.5 对计算结果进行合并

3.5.1 thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)

作用:返回一个新的CompletionStage,当该阶段和另一个给定阶段都正常完成时,该阶段将以两个结果作为所提供函数的参数执行。

方法如下:

public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn) {
    
    
    return biApplyStage(null, other, fn);
}

示例:

public static void main(String[] args) {
    
    
    CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
    
    
        log.info("{}\t ---A启动" + Thread.currentThread().getName());
        //暂停几秒钟线程
        try {
    
    
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        return 20;
    });

    CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
    
    
        log.info("{}\t ---B启动" + Thread.currentThread().getName());
        //暂停几秒钟线程
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        return 10;
    });


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

    log.info("{}",result.join());
}

运行结果如下:

image-20230403000352304

猜你喜欢

转载自blog.csdn.net/Decade_Faiz/article/details/129924469