目录
4.5、使用CompletableFuture完成电商大比价
一、Future接口
Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果,取消任务的执行,判断任务是否被取消,判断任务执行是否完毕等。
如:主线程让一个子线程去执行任务,子线程在执行过程中过于耗时,启动子线程开始执行任务后,主线程就去做其他事情,当主线程将需要其他实现做完后,等一会再去获取子任务系统的执行结果或变更的任务状态。
小结:Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的复杂业务。
二、Future接口的功能
Future是Java5新加的一个接口,它提供了一种异步并行计算功能。如果主线程需要执行一个很耗时的计算任务,我们可以通过Future把这个任务放到异步线程中执行。主线程继续处理其他任务或者先行结束,再通过Future获取计算结果。
目的:异步多线程任务执行且返回结果,三个特点:多线程、有返回、异步任务
三、FutureTask
架构图 :
使用并实现异步任务:
public class FutureTest {
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("调用call()。。。。");
return "hello year";
}
}
优点:future+线程池异步多线程任务配合,能显著提高程序的执行效率
public class FutureTaskPoolTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池
ExecutorService executorService = 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";
});
executorService.submit(task1);
FutureTask<String > task2 = new FutureTask<String>(() -> {
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "task2 over";
});
executorService.submit(task2);
System.out.println(task1.get());
System.out.println(task2.get());
FutureTask<String > task3 = new FutureTask<String>(() -> {
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "task3 over";
});
executorService.shutdown();
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime - startTime)+"毫秒");//耗时:563毫秒
System.out.println(Thread.currentThread().getName()+"\t .....end");
}
public static void m(){
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(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime - startTime)+"毫秒");//耗时:1022毫秒
System.out.println(Thread.currentThread().getName()+"\t .....end");
}
}
缺点:get()阻塞,一旦调用get()方法求结果,如果计算没有完成很容易导致程序阻塞。
public class FutureTask01 {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
FutureTask<String> task = new FutureTask<String>(() -> {
System.out.println(Thread.currentThread().getName()+"\t ...start");
TimeUnit.SECONDS.sleep(5);
return "task ok";
});
Thread thread = new Thread(task,"task");
thread.start();
System.out.println(Thread.currentThread().getName()+"\t ...其他任务");
//System.out.println(task.get());
//也可以设置超时不继续等
System.out.println(task.get(4,TimeUnit.SECONDS));
}
}
isDone()轮询:轮询的方式会耗费无谓的CPU资源,而且也不见得能及时地得到计算结果。如果想要异步获取结果,通常都会以轮询地方式去获取结果尽量不要阻塞。
public class FutureTask01 {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
FutureTask<String> task = new FutureTask<String>(() -> {
System.out.println(Thread.currentThread().getName()+"\t ...start");
TimeUnit.SECONDS.sleep(5);
return "task ok";
});
Thread thread = new Thread(task,"task");
thread.start();
System.out.println(Thread.currentThread().getName()+"\t ...其他任务");
//System.out.println(task.get());
//也可以设置超时不继续等
//System.out.println(task.get(4,TimeUnit.SECONDS));
while (true){
if (task.isDone()){
System.out.println(task.get());
break;
}else {
TimeUnit.MILLISECONDS.sleep(500);
System.out.println("正在拼命处理中。。。");
}
}
}
}
总结:Future对于结果地获取不是很友好,只能通过阻塞或轮询地方式获取结果
四、CompletableFuture背景及使用
get()方法在Future计算完成之前一直处于阻塞状态下,isDone()方法容易耗费CPU资源,对于真正地异步处理我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果。
阻塞的方式和异步编程的设计理念违背,而轮询的方式会耗费无谓的CPU资源。因此,JDK8设计出CompletableFuture。
CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方
类架构图:
4.1、CompletionStage
代表异步计算过程中的某一个阶段,一个阶段完成后可能会触发另外一个阶段,有些类似Linux系统的管道分割符传递参数。
一个阶段的计算执行可以是一个Future,Consumer,或者Runnable。
一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发。
4.2、CompletableFuture
1、在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法。
2、它可能代表一个明确完成的Future,也可能代表一个完成阶段(CompletionStage),它支持在计算完成以后触发一些函数和执行某些动作。
3、它实现了Future和CompletionStage接口。
4.3、四个静态方法
runAsync无返回值:
public static CompletableFuture<Void> runAsync(Runnable runnable) public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)
public class CompletableFutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(completableFuture.get());
}
}
public class CompletableFutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(3);
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
},pool);
System.out.println(completableFuture.get());
pool.shutdown();
}
}
supplyAsync有返回值:
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)
public class CompletableFutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// ExecutorService pool = Executors.newFixedThreadPool(3);
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "future ok";
});
System.out.println(completableFuture.get());
}
}
public class CompletableFutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(3);
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "future ok";
},pool);
System.out.println(completableFuture.get());
pool.shutdown();
}
}
上述Executor executor参数:没有指定Executor的方法,直接使用默认的ForkJoinPool.commonPool()作为它的线程池执行异步代码。如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码。
4.4、减少阻塞和轮询
从Java8开始引进了CompletableFuture,它是Future的功能增强版,减少阻塞和轮询可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。
public class CompletableFutureDemo {
public static void main(String[] args) {
//线程池建议自己创建
ExecutorService pool = Executors.newFixedThreadPool(3);
try {
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
int result = ThreadLocalRandom.current().nextInt(20);
if (result > 5){
int i = 1/0;
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("计算结果需要1秒" + result);
return result;
},pool).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() + "先去执行别的任务");
} catch (Exception e) {
e.printStackTrace();
} finally {
pool.shutdown();
}
//主线程不要立刻结束,不然CompletableFuture使用的线程池会立刻关闭掉,这里可先暂停3秒
// try {
// TimeUnit.SECONDS.sleep(3);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
}
优点:1、异步任务结束时,会自动回调某个对象的方法。2、主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行。3、异步任务出错时,会自动回调某个对象的方法。
4.5、使用CompletableFuture完成电商大比价
public class CompletableFutureMall {
static List<NetMall> list = Arrays.asList(
new NetMall("jd"),
new NetMall("taobao"),
new NetMall("dangdang")
);
//一家家搜索
public static List<String> getPrice(List<NetMall> list,String productName){
return list
.stream()
.map(netMall ->
String.format(productName + " in %s price is %.2f",
netMall.getNetMallName(),
netMall.calcPrice(productName)))
.collect(Collectors.toList());
}
//异步搜索
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 start = System.currentTimeMillis();
List<String> stringList = getPrice(list, "java");
for (String element:stringList) {
System.out.println(element);
}
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "毫秒");
System.out.println("改进后的方法:");
long start1 = System.currentTimeMillis();
List<String> strings = getPriceByCompletableFuture(list, "java");
for (String element:strings) {
System.out.println(element);
}
long end1 = System.currentTimeMillis();
System.out.println("耗时:" + (end1 - start1) + "毫秒");
}
}
class NetMall{
private String netMallName;
public String getNetMallName() {
return 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);
}
}
五、CompletableFuture常用API
5.1、获取结果
public class CompletableFutureAPI {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "计算完成";
});
// System.out.println(completableFuture.get());//调用就必须给值
// System.out.println(completableFuture.get(0,TimeUnit.SECONDS));//调用后,只获得等待时间内的值
// System.out.println(completableFuture.join());//与get方法一样
System.out.println(completableFuture.getNow("替代值111"));//立即获取结果不阻塞,计算完,返回计算完的值,否则就返回替代值
}
}
5.2、主动触发计算
public class CompletableFutureAPI {
public static void main(String[] args) {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "计算完成";
});
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//当调用completableFuture.join()被阻塞时,complete方法返回true,并将里面的值赋给join方法并返回
System.out.println(completableFuture.complete("yyds" )+"\t" + completableFuture.join());
}
}
5.3、对计算结果进行处理
thenApply:计算结果存在依赖关系,这两个线程串行化
public class CompletableFutureAPI2 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("111");
return 1;
},pool).thenApply(f ->{
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;
});
pool.shutdown();
}
}
由于存在依赖关系,当前步骤有异常的话就叫停。
public class CompletableFutureAPI2 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("111");
return 1;
},pool).handle((f,e) ->{
System.out.println("222");
int i = 1/0;
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;
});
pool.shutdown();
}
}
有异常也可以往下一步走,根据带的异常参数可以进。
5.4、对计算结果进行消费
public class CompletableFutureAPI3 {
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());
}
}
CompletableFuture与线程池的选择
public class CompletableFutureAPI4 {
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(6);
try {
CompletableFuture.supplyAsync(() ->{
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1号任务\t"+Thread.currentThread().getName());
return "aa";
},pool).thenRunAsync(()->{
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2号任务\t"+Thread.currentThread().getName());
}).thenRun(()->{
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3号任务\t"+Thread.currentThread().getName());
}).thenRun(()->{
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4号任务\t"+Thread.currentThread().getName());
});
} catch (Exception e) {
e.printStackTrace();
} finally {
pool.shutdown();
}
TimeUnit.SECONDS.sleep(1);
}
}
1、没有传入自定义线程池,都使用默认线程池。
2、传入了一个自定义线程池,当执行第一个任务的时候,传入一个自定义线程池:调用thenRun方法执行第二个任务时,则第二个任务和第一个任务是共用一个线程池。调用thenRunAsync执行第二个任务时,则第一个任务使用的是自己传入的线程池,第二个任务使用是默认线程池。
3、有可能系统处理太快,系统优化切换原则,直接使用main线程处理。
5.5、对计算速度进行选用
public class CompletableFutureAPI5 {
public static void main(String[] args) {
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "A完成了";
});
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "B完成了";
});
CompletableFuture<String> completableFuture = futureA.applyToEither(futureB, f -> {
return f + "领先";
});
System.out.println(Thread.currentThread().getName() + "\t" + completableFuture.join());
}
}
5.6、对计算结果合并
public class CompletableFutureAPI6 {
public static void main(String[] args) {
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 10;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 20;
});
CompletableFuture<Integer> result = future1.thenCombine(future2, (x, y) -> {
System.out.println("两个结果开始合并");
return x + y;
});
System.out.println(result.join());
}
}