我们知道,在java中如果实现异步编程,可以使用线程池来实现,也可以使用老的future来实现;但是他们在使用中会有很多的不足,比如:等待执行结果时我们必须要使用其他的实现方案或者使用future的get()方法;无法解决异步线程间的依赖关系;多个future不能合并起来;没有异常处理机制。以上这些痛点在CompletableFuture中都已经给出了很好的解决。下面通过api的使用来讲解相关内容:
// 提交有返回值任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
return "Hello,world!";
}
});
System.out.println(future.get());
// 提交没有返回值任务
CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
System.out.println("Hello,world!");
}
}).get();
CompletableFuture默认使用的是ForkJoinPool.commonPool
线程池,线程大小是与CPU核数一致的,这样就有可能不满足业务的需求,我们可以使用自己定义的线程池,
// 自定义线程池
ForkJoinPool pool = new ForkJoinPool(64);
// 提交有返回值任务并指定线程池
CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
System.out.println(Thread.currentThread().getName());
return "Hello,world!";
}
}, pool);
System.out.println(future.get());
// 提交没有返回值任务并指定线程池
CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
System.out.println("Hello,world!");
}
}, pool).get();
Async
结尾的方法,它表示在新的线程中执行,也可以指定新的线程池执行任务。示例如下:// 自定义线程池
ForkJoinPool pool = new ForkJoinPool(64);
// 提交有返回值任务并指定线程池
CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
System.out.println("第一步:" + Thread.currentThread().getName());
return "Hello,world!";
}
}, pool).thenApply(new Function<String, String>() {
@Override
public String apply(String s) {
System.out.println("第二步:" + Thread.currentThread().getName());
return s + "--1";
}
}).thenApplyAsync(new Function<String, String>() {
@Override
public String apply(String s) {
System.out.println("第三步:" + Thread.currentThread().getName());
return s + "--2";
}
}, pool).thenAccept(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("第四步:" + Thread.currentThread().getName());
System.out.println(s);
}
}).thenAcceptAsync(new Consumer<Void>() {
@Override
public void accept(Void unused) {
System.out.println("第五步:" + Thread.currentThread().getName());
}
}, pool).thenRun(new Runnable() {
@Override
public void run() {
System.out.println("第六步:" + Thread.currentThread().getName());
}
}).thenRunAsync(new Runnable() {
@Override
public void run() {
System.out.println("第七步:" + Thread.currentThread().getName());
}
}, pool);
// 自定义线程池
ForkJoinPool pool = new ForkJoinPool(64);
// 开始时间
long start = System.currentTimeMillis();
// 任务1
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
try {
// 等待1s后返回结果,模拟耗时任务
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello,world!";
}
}, pool);
// 任务2
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
try {
// 等待500ms后返回结果,模拟耗时任务
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello,java!";
}
}, pool);
// 等待两个future执行完成后进行下一步操作
future1.thenCombine(future2, new BiFunction<String, String, String>() {
@Override
public String apply(String s, String s2) {
return s + " + " + s2;
}
}).thenAccept(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
System.out.println("use time : " + (System.currentTimeMillis() - start) + "ms");
}
}).get();
(2)等待多个任务执行完成才进行下一个任务,上面的方式就不能满足了,但是CompletableFuture已经为我们提供了allOf()方法来满足这种场景的需求,使用示例如下:
// 自定义线程池
ForkJoinPool pool = new ForkJoinPool(64);
// 开始时间
long start = System.currentTimeMillis();
// 任务1
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
try {
// 等待1s后返回结果,模拟耗时任务
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello,world!";
}
}, pool);
// 任务2
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
try {
// 等待500ms后返回结果,模拟耗时任务
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello,java!";
}
}, pool);
// 任务3
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
try {
// 等待300ms后返回结果,模拟耗时任务
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello,boy!";
}
}, pool);
// 等待多个future都完成后进行下一步操作
CompletableFuture.allOf(future1, future2, future3).thenRun(new Runnable() {
@Override
public void run() {
try {
System.out.println(future1.get() + " + " + future2.get() + " + " + future3.get());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("use time : " + (System.currentTimeMillis() - start) + "ms");
}
}).get();
我这里只是简单的示例代码,其实我们可以将这些future存放在数组中,通过数组循环遍历使用:
// 等待多个future都完成后进行下一步操作
CompletableFuture[] futures = { future1, future2, future3 };
CompletableFuture.allOf(futures).thenRun(new Runnable() {
@Override
public void run() {
try {
StringBuilder builder = new StringBuilder();
for(int i = 0; i < futures.length; i++) {
builder.append(" + ").append(futures[i].get());
}
System.out.println(builder.substring(3));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("use time : " + (System.currentTimeMillis() - start) + "ms");
}
}).get();
(3)两个任务只要有一个执行完成就可以进行下一步操作,可以使用 applyToEither() 或 applyToEitherAsync 、
acceptEither() 或 acceptEitherAsync() 、runAfterEither 或 runAfterEitherAsync 来实现,他们分别对应使用上一步的结果且有返回值、消费上一步的结果没有返回值、不使用上一步的结果且没有返回值。这几个api使用很相似,简单示例一个的使用方式:
future1.acceptEither(future2, new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
System.out.println("use time : " + (System.currentTimeMillis() - start) + "ms");
}
}).get();
(4)当任务数很多且只要有一个任务返回结果就可以进行下一步了,这时就可以使用anyOf()方法:
// 等待多个future结果,只要有一个完成就进行下一步操作
CompletableFuture[] futures = { future1, future2, future3 };
CompletableFuture.anyOf(futures).thenAccept(new Consumer<Object>() {
@Override
public void accept(Object obj) {
System.out.println(obj);
System.out.println("use time : " + (System.currentTimeMillis() - start) + "ms");
}
}).get();
CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
int rs = 5 / 0;
return "Hello,world!";
}
}).exceptionally(new Function<Throwable, String>() {
@Override
public String apply(Throwable throwable) {
return "exception";
}
}).thenApply(new Function<String, String>() {
@Override
public String apply(String s) {
return "result data : " + s;
}
});
System.out.println("任务结果:" + future.get());
(2)使用handle()实现对异常的捕获,不论程序是否发生异常,handle()方法都会被执行到,我们可以通过判断是否抛出异常来做相应的处理:
CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
int rs = 5 / 0;
return "Hello,world!";
}
}).handle(new BiFunction<String, Throwable, String>() {
@Override
public String apply(String s, Throwable throwable) {
if(throwable == null) {
return s;
}
return "exception";
}
}).thenApply(new Function<String, String>() {
@Override
public String apply(String s) {
return "result data : " + s;
}
});
System.out.println("任务结果:" + future.get());
对比exceptionally()和handle()方法,exceptionally()方法在发生异常时返回的是异常内对应的值,我们得不到调用链上之前的运行结果;handle()方法在程序运行过程中发生异常时会把异常对象抛出同时也会把调用链上的执行结果返回,通过判断异常对象是否为空来确认是否发生了异常,由于有上一步的结果数据可以让程序继续运行。
String rs1 = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
return "Hello,world!";
}
}).get();
System.out.println(rs1);
String rs2 = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
return "Hello,java!";
}
}).join();
System.out.println(rs2);
上面总结的内容基本涵盖了CompletableFuture全部内容,在进行异步编程时,CompletableFuture相比于老的Future更加好用,它提供了很多api处理常见的业务场景,结合lambda表达式和流式编程使得异步处理非常方便。