前言
今天是1024,祝各位老板节日快乐!
傍晚打算下楼买点吃的,心中打定了注意,今晚要买【鸡蛋仔】和【手抓饼】,好家伙,这不素材就有了嘛,嘿嘿嘿!
于是我便迈着“六亲不认”的步伐出发了…
能不能再快点
❓路上,我思考了一个问题。那就是我到底需要耗费多长时间,才能握住简单的快乐?
按照流程是这样子的
- 骑着我心爱的小摩托出发
- 到达买鸡蛋仔的摊位,点一份鸡蛋仔,然后等老板做完
- 再到手抓饼摊位,点一份手抓饼
- 拿到手抓饼,骑车回家
假设路程来回是6分钟,制作鸡蛋仔的时间是10分钟,制作手抓饼的时间的5分钟
那么我总共就需要【21】分钟,才可以坐在电脑旁,看剧干饭
如果想要耗时短一点,那么上面这个流程就不是我们需要的,而应该是这样子的
- 骑着我心爱的小摩托出发
- 到达买鸡蛋仔的摊位,点一份鸡蛋仔,跟老板说,我待会来拿
- 再到手抓饼摊位,点一份手抓饼
- 拿到手抓饼之后,再返回鸡蛋仔摊位,等待取鸡蛋仔
- 骑车回家
那么此时,我们耗费的时间就只有【16】分钟,因为这次我在点了鸡蛋仔之后,老板在烹饪的期间,先去完成了一样耗时在10分钟之内的操作,就是买我要的手抓饼
当然,我肯定是用第二种方式的。。。
落地一下
梳理一下,刚才的流程
- 第一种流程就是典型,按照顺序执行
- 而第二种流程就是使用到了异步的思想
那么不妨来敲下代码,将流程给展现出来
首先我们得有一个买吃的对象
/**
* @Author: Amg
* @Date: Created in 20:22 2021/10/24
* @Description: 构造“买点吃的”对象
*/
public class BuySomethingToEat {
/**
* 食物名称
*/
private String foodName;
/**
* 制作时长单位
*/
private TimeUnit unit;
/**
* 制作时长
*/
private Long waitSecond;
public BuySomethingToEat(String foodName, TimeUnit unit, Long waitSecond) {
this.foodName = foodName;
this.unit = unit;
this.waitSecond = waitSecond;
}
@Override
public String toString() {
return new StringJoiner(", ", BuySomethingToEat.class.getSimpleName() + "[", "]")
.add("foodName='" + foodName + "'")
.add("unit=" + unit)
.add("waitSecond=" + waitSecond)
.toString();
}
/**
* 处理食材
* @return
*/
public String handlerFood() {
System.out.println(String.format("正在准备食物:[%s],单位:[%s],等待时长:[%s] ",foodName,waitSecond,unit.toString()));
try {
unit.sleep(waitSecond);
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.toString() + " is done!";
}
}
按照顺序的思想执行
/**
* @Author: Amg
* @Date: Created in 21:09 2021/10/24
* @Description: 常规做法
*/
public class Convention {
public static void main(String[] args) throws Exception {
final long start = System.currentTimeMillis();
//我们不可能有时间真的等待上10分钟,所以用秒来替代
final BuySomethingToEat eatOne = new BuySomethingToEat("鸡蛋仔", TimeUnit.SECONDS, 10L);
final BuySomethingToEat eatTwo = new BuySomethingToEat("手抓饼", TimeUnit.SECONDS, 5L);
System.out.println(eatOne.handlerFood());
System.out.println(eatTwo.handlerFood());
System.out.println("买齐了,回家!");
//最后加上来回的6分钟
System.out.println("总共耗时:" + ((System.currentTimeMillis() - start) / 1000 + 6));
}
}
//output
正在准备食物:[鸡蛋仔],单位:[10],等待时长:[SECONDS]
BuySomethingToEat[foodName='鸡蛋仔', unit=SECONDS, waitSecond=10] is done!
正在准备食物:[手抓饼],单位:[5],等待时长:[SECONDS]
BuySomethingToEat[foodName='手抓饼', unit=SECONDS, waitSecond=5] is done!
买齐了,回家!
总共耗时:15
按照异步的思想执行
异步的思想
在引入今天的主角之前,先来理解一下异步的思想,还是先来一个图
异步的思想就是:准备好了,再告诉我,我先去干其他的事情
对应图上就是,在鸡蛋仔制作中,由于耗时比较久,我就不在这里干等了,先去点手抓饼(完成其他耗时没有那么久的操作)
今天的猪脚,jdk中的Future模式
由于Future模式(核心思想就是异步调用)非常的常用,因此JDK内部已经为我们准备好了实现
简单说,通过Future,你可以定义一个任务,让这个任务执行,在未来某个时间,可以通过API获取到数据,如果获取不到,就会一直阻塞
//Future接口里面一共提供了5个方法,想了解更多?那就直接去阅读这个接口的文档就好了
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
这是个接口,而我们真正使用到的是它的子类FutureTask
我们观察一下,FutureTask这个类
可以发现,他的构造方法需要传入一个实现了Callable的类或者实现了Runnable的类,既然如此,我们就再新建一个BuyTask好了
/**
* @Author: Amg
* @Date: Created in 21:15 2021/10/24
* @Description: TODO
*/
public class BuyTask implements Callable<String> {
private BuySomethingToEat buySomethingToEat;
public BuyTask(BuySomethingToEat buySomethingToEat) {
this.buySomethingToEat = buySomethingToEat;
}
@Override
public String call() throws Exception {
return buySomethingToEat.handlerFood();
}
}
然后,使用FutureTask这个类创建两个任务交由线程池去执行
public class MyFutureTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
final BuySomethingToEat eatOne = new BuySomethingToEat("手抓饼", TimeUnit.SECONDS, 10L);
final BuySomethingToEat eatTwo = new BuySomethingToEat("鸡蛋仔", TimeUnit.SECONDS, 5L);
final long start = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(2);
final FutureTask<String> task1 = new FutureTask<>(new BuyTask(eatOne));
final FutureTask<String> task2 = new FutureTask<>(new BuyTask(eatTwo));
executorService.submit(task1);
executorService.submit(task2);
System.out.println(task2.get());
System.out.println(task1.get());
System.out.println("买齐了");
System.out.println("总共耗时:" + (System.currentTimeMillis() - start) / 1000);
if (!executorService.isShutdown()) executorService.shutdown();
}
}
//output
正在准备食物:[鸡蛋仔],单位:[5],等待时长:[SECONDS]
正在准备食物:[手抓饼],单位:[10],等待时长:[SECONDS]
BuySomethingToEat[foodName='鸡蛋仔', unit=SECONDS, waitSecond=5] is done!
BuySomethingToEat[foodName='手抓饼', unit=SECONDS, waitSecond=10] is done!
买齐了
总共耗时:16
//或者是
正在准备食物:[手抓饼],单位:[10],等待时长:[SECONDS]
正在准备食物:[鸡蛋仔],单位:[5],等待时长:[SECONDS]
BuySomethingToEat[foodName='鸡蛋仔', unit=SECONDS, waitSecond=5] is done!
BuySomethingToEat[foodName='手抓饼', unit=SECONDS, waitSecond=10] is done!
买齐了
总共耗时:16
总结
Future模式用途非常的广泛
- 常见的有SQL异步调用,当某个SQL耗时稍微久一点,可以先执行与SQL流程不相关的其他步骤先,节省时间;
- 还有计算计算密集的操作
- 还有异步调用其他系统接口
Future还有更加高级的用法,当前只是简单介绍了一下Future以及他的实现类FutureTask是怎么使用起来的,对于理解Future思想和简单使用也是足够得了
但是关于Future还会有更加高级的用法,例如guava中对Future的增强,后续也会进一步深入的介绍