文章目录
- Future方法
-
- 同步执行
- 异步执行
- 改进的CompletableFuture方法
-
- 改进的点
- 同步与异步执行的例子
Future方法
同步执行
大家好,在Java8之前使用Future的一个例子,例如一个人执行洗衣服和拖地的行为,如果是串行的执行,我们可以用以下的伪代码进行表示
public class FutureTest0 {
public static void main(String[] args) {
long start = System.nanoTime();System.out.println(Wash());System.out.println(Sweep());long duration = (System.nanoTime() - start) / 1000000;System.out.println(duration);}static String Wash() {
try {
Thread.sleep(1000L);} catch (InterruptedException e) {
e.printStackTrace();}return "衣服洗好了";}static String Sweep() {
try {
Thread.sleep(1000L);} catch (InterruptedException e) {
e.printStackTrace();}return "地扫好了";}
}
大家来看下这段代码的执行时间
衣服洗好了
地扫好了
2005 msecs
异步执行
但是在现实生活中我们都知道,洗衣服和扫地这两个行为完全可以并行执行,这就是一个 Future事件,Future相比于底层的Thread更为易用,下面展示了其用法
package com.java8inaction.chapter11.FutureLearn;
import java.util.concurrent.*;
ic class FutureTest0 {
public static void main(String[] args) {
// 创建一个可缓存的线程池ExecutorService executorService = Executors.newCachedThreadPool();long start = System.nanoTime();// 向线程池提交并行任务Future<String> future = executorService.submit(new Callable<String>() {
@Overridepublic String call() throws Exception {
// 以异步的方式在新的线程中执行其他任务return Wash();}});System.out.println(Sweep());try {
// 设置等待时间为1s,超过这个时间后主线程视这个并行任务阻塞了,就放弃这个任务String res = future.get(1, TimeUnit.SECONDS);System.out.println(res);} catch (InterruptedException e) {
System.out.println("等待过程中任务中断");e.printStackTrace();} catch (ExecutionException e) {
System.out.println("计算抛出异常");e.printStackTrace();} catch (TimeoutException e) {
System.out.println("并行任务执行超时");e.printStackTrace();}long duration = (System.nanoTime() - start) / 1000000;System.out.println(duration);// 关闭线程池executorService.shutdown();}static String Wash() {
try {
Thread.sleep(1000L);} catch (InterruptedException e) {
e.printStackTrace();}return "衣服洗好了";}static String Sweep() {
try {
Thread.sleep(1000L);} catch (InterruptedException e) {
e.printStackTrace();}return "地扫好了";}
}
这段代码是符合了现实生活中人的行为方式,开辟的线程池就相当于洗衣房,我们向洗衣房提交了一个自助洗衣的任务,预定时间是假设是10分钟(代码里以毫秒为单位),在这个时间内我们可以去扫地,扫地大概也是10分钟,所以当10分钟过去后,我们应该也会收到衣服洗好的消息,这样时间就缩短了一半,我们来看下执行的结果
地扫好了
衣服洗好了
1003
但是有一个很现实的问题是,如果我们洗的衣服太多,最后10分钟都不可能洗的完,或者是洗衣机突然崩掉了,我们扫完地又等了很长时间,这种情况在现实编程中很常见,譬如我们异步的去调用一个远程接口,但是这个接口在其自己的进程中执行时突然异常,在这里,Future提供了 Get() 方法,他可以接受一个超时的参数,如果超过这个时间,就会抛出一个TimeoutException 异常,现在我们尝试将代码中执行洗衣服的时间改为4s,可以看到这个超时异常
地扫好了
并行任务执行超时
2007
我们可以用图来描述这整个过程
改进的CompletableFuture方法
改进的点
在实际现实中,并行任务往往要更复杂,java8之后,出现了一个新的类CompletableFuture,它实现了Future接口,同时适用于更高级的场景
- 将两个异步计算合并为一个(这两个异步任务相互独立,但第二个又依赖于第一个)
- 等待Future集合中所有任务都完成
- 仅仅等待Future集合中最快的任务完成
- 手动设定异步的操作
- 当Future完成能收到通知从而进行下一步操作,不再是简单阻塞
同步与异步执行的例子
现在,我们实现一个查询商品价格列表的api,在此过程中,我们增加了一个访问远程获取商品打折券的操作,这里用一个delay() 函数来模拟这个过程
Shop类
package com.java8inaction.chapter11.FutureLearn;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;public class Shop {
private String name;private double price;public String getName() {
return name;}public void setName(String name) {
this.name = name;}public double getPrice() {
return price;}public Shop(String bestShop) {
this.name = bestShop;}// 下面是异步的调用获取打折券方法public Future<Double> getPriceAsync(String product) {
CompletableFuture<Double> futurePrice = new CompletableFuture<Double>();new Thread(()->{
try{
// 开始异步计算double price = calculatePrice(product);// 设置超时时间futurePrice.get(1, TimeUnit.SECONDS);// 完成任务futurePrice.complete(price);}catch (Exception e) {
futurePrice.completeExceptionally(e);}}).start();return futurePrice;}// 同步调用获取打折券方法public Double getPriceSycronize(String product) {
double price = calculatePrice(product);return price;}// 模拟去获取打折券public void getdiscount(){
System.out.println("查询远程打折券数据");try{
Thread.sleep(2000L);}catch (InterruptedException e) {
throw new RuntimeException(e);}System.out.println("打折券拿到了!");}// 获取商品价格private double calculatePrice(String product) {
delay();// 随机返回一个价格值return new Random().nextDouble() * product.charAt(0) + product.charAt(1);}// 模拟查询数据库产生的时延public void delay() {
System.out.println("正在查询数据库...");try{
Thread.sleep(1000L);}catch (InterruptedException e) {
throw new RuntimeException(e);}}
}
ShopTest类
package com.java8inaction.chapter11.FutureLearn;import java.util.concurrent.Future;public class ShopTest {
public static void main(String[] args) {
asyncMethod();sycronizedMethod();}static void asyncMethod() {
System.out.println("开启异步执行...");Shop shop = new Shop("天猫商城");long start = System.nanoTime();Future<Double> futurePrice = shop.getPriceAsync("Oranges");// 执行更多任务shop.getdiscount();// 计算商品价格的同时try{
double price = futurePrice.get();System.out.printf("查到商品价格为 %.2f%n",price);}catch (Exception e) {
throw new RuntimeException(e);}long retrievalTime = ((System.nanoTime() - start) / 1000000);System.out.println("耗时 " + retrievalTime + " msecs");}static void sycronizedMethod() {
System.out.println("开启同步执行...");Shop shop = new Shop("天猫商城");long start = System.nanoTime();Double price = shop.getPriceSycronize("Oranges");System.out.printf("查到商品价格为 %.2f%n",price);shop.getdiscount();long retrievalTime = ((System.nanoTime() - start) / 1000000);System.out.println("耗时 " + retrievalTime + " msecs");}
}
开启异步执行…
查询远程打折券数据
正在查询数据库…
打折券拿到了!
查到商品价格为 188.33
耗时 2058 msecs
开启同步执行…
正在查询数据库…
查到商品价格为 159.73
查询远程打折券数据
打折券拿到了!
耗时 3007 msecs
可以明显看到使用异步api操作带来的效率的提升
当然也可以用函数式语法糖去精简代码
public Future<Double> getPriceAsync(String product) {
return CompletableFuture.supplyAsync(() -> calculatePrice(product));}