怎么用wait、notify巧妙的设计一个Future模式?
我们知道多线程可以实现同时执行多个任务(只是看起来是同时,其实是CPU的时间片切换特别快我们没感觉而已)。
现在假设一个做饭的场景,你没有厨具也没有食材。你可以去网上买一个厨具,但是这段时间,你不需要闲着啊,可以同时去超市买食材。
设想这是两个线程,主线程去买食材,然后开启一个子线程去买厨具。但是,子线程是需要返回一个厨具的。 如果用普通的线程,只有一个Run方法,而Run方法是没有返回值的,这个时候该怎么办呢?
我们就可以用JDK提供的Future模式。在主线程买完食材之后,可以主动去获取子线程的厨具。(本文认为读者了解Future,因此不对Future用法做过多介绍)
代码如下:
public class FutureCook {
static class Chuju {
}
static class Shicai{
}
public static void cook(Chuju chuju,Shicai shicai){
System.out.println("最后:烹饪中...");
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
//第一步,网购厨具
Callable<Chuju> shopping = new Callable<Chuju>(){
@Override
public Chuju call() throws Exception {
System.out.println("第一步:下单");
System.out.println("第一步:等待送货");
Thread.sleep(5000); //模拟送货时间
System.out.println("第一步:快递送到");
return new Chuju();
}
};
FutureTask<Chuju> task = new FutureTask<Chuju>(shopping);
new Thread(task).start();
//第二步,购买食材
Thread.sleep(2000);
Shicai shicai = new Shicai();
System.out.println("第二步:食材到位");
//第三步,烹饪
if(!task.isDone()){ //是否厨具到位
System.out.println("第三步:厨具还没到,请等待,也可以取消");
//①
// task.cancel(true);
// System.out.println("已取消");
// return;
}
//尝试获取结果,如果获取不到,就会进入等待状态
// 即main线程等待子线程执行结束才能继续往下执行
Chuju chuju = task.get();
System.out.println("第三步:厨具到位,可以烹饪了");
cook(chuju,shicai);
}
}
返回结果:
第一步:下单
第一步:等待送货
第二步:食材到位
第三步:厨具还没到,请等待,也可以取消
第一步:快递送到
第三步:厨具到位,可以烹饪了
最后:烹饪中...
以上代码表示,子线程购买厨具消耗的时间比较长(假定5秒),而主线程购买食材比较快(2秒),所以我在第三步烹饪之前,先去判断一下买厨具的线程是否执行完毕。此处肯定返回false,然后主线程可以选择继续等待,也可以选择取消。(把①注释打开即可测试取消)
我们可以看到,利用Future模式,可以把原本同步执行的任务改为异步执行,可以充分利用CPU资源,提高效率。
现在,我用wait、notify的方式来实现和以上Future模式一模一样的效果。
大概思想就是,创建一个FutureClient端去发起请求,通过FutureData先立即返回一个结果(此时相当于只返回一个请求成功的通知),然后再去开启一个线程异步地执行任务,获取真实数据RealData。此时,主线程可以继续执行其他任务,当需要数据的时候,就可以调用get方法拿到真实数据。
1)定义一个数据接口,包含获取数据的get方法,判断任务是否执行完毕的isDone方法,和取消任务的cancel方法。
public interface Data<T> {
T get();
boolean isDone();
boolean cancel();
}
2)定义真实数据的类,实现Data接口,用来执行实际的任务和返回真实数据。
public class RealData<T> implements Data<T>{
private T result ;
public RealData (){
this.prepare();
}
private void prepare() {
//准备数据阶段,只有准备完成之后才可以继续往下走
try {
System.out.println("第一步:下单");
System.out.println("第一步:等待送货");
Thread.sleep(5000);
System.out.println("第一步:快递送到");
} catch (InterruptedException e) {
System.out.println("被中断:"+e);
//重新设置中断状态
Thread.currentThread().interrupt();
}
Main.Chuju chuju = new Main.Chuju();
result = (T)chuju;
}
@Override
public T get() {
return result;
}
@Override
public boolean isDone() {
return false;
}
@Override
public boolean cancel() {
return true;
}
}
prepare方法用来准备数据,其实就是执行的实际任务。get方法用来返回任务的执行结果。
3)定义一个代理类FutureData用于给请求端FutureClient暂时返回一个假数据。等真实数据拿到之后,再装载真实数据。
public class FutureData<T> implements Data<T>{
private RealData<T> realData ;
private boolean isReady = false;
private Thread runningThread;
public synchronized void setRealData(RealData realData) {
//如果已经装载完毕了,就直接返回
if(isReady){
return;
}
//如果没装载,进行装载真实对象
this.realData = realData;
isReady = true;
//进行通知
notify();
}
@Override
public synchronized T get() {
//如果没装载好 程序就一直处于阻塞状态
while(!isReady){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//装载好直接获取数据即可
return realData.get();
}
public boolean isDone() {
return isReady;
}
@Override
public boolean cancel() {
if(isReady){
return false;
}
runningThread.interrupt();
return true;
}
public void setRunningThread(){
runningThread = Thread.currentThread();
}
}
如果get方法被调用,就会去判断数据是否已经被加载好(即判断isReady的值),如果没有的话就调用wait方法进入等待。
setRealData用于去加载真实的数据,加载完毕之后就把isReady设置为true,然后调用notify方法通知正在等待的线程。此时,get方法收到通知就继续执行,然后返回真实数据realData.get().
另外也简单的实现了一个取消任务的方法cancel,去中断正在执行子任务的线程。
4)FutureClient客户端用于发起请求,异步执行任务。
public class FutureClient {
public Data call(){
//创建一个代理对象FutureData,先返回给客户端(无论是否有值)
final FutureData futureData = new FutureData();
//启动一个新的线程,去异步加载真实的对象
new Thread(new Runnable() {
@Override
public void run() {
//此处注意需要记录一下异步加载真实数据的线程,以便后续可以取消任务。
futureData.setRunningThread();
RealData realData = new RealData();
//等真实数据处理完毕之后,把结果赋值给代理对象
futureData.setRealData(realData);
}
}).start();
return futureData;
}
}
5)测试
public class Main {
static class Chuju{
}
static class Shicai{
}
public static void main(String[] args) throws InterruptedException {
FutureClient fc = new FutureClient();
Data data = fc.call();
Thread.sleep(2000);
Shicai shicai = new Shicai();
System.out.println("第二步:食材到位");
if(!data.isDone()){
System.out.println("第三步:厨具还没到,请等待或者取消");
//②
// data.cancel();
// System.out.println("已取消");
// return;
}
//真正需要数据的时候,再去获取
Chuju chuju = (Chuju)data.get();
System.out.println("第三步:厨具到位,可以烹饪了");
cook(chuju,shicai);
}
public static void cook (Chuju chuju, Shicai shicai){
System.out.println("最后:烹饪中...");
}
}
执行结果和用JDK提供的Future模式是一模一样的。我们也可以把②出的代码打开,测试任务取消的结果。
第一步:下单
第一步:等待送货
第二步:食材到位
第三步:厨具还没到,请等待或者取消
已取消
被中断:java.lang.InterruptedException: sleep interrupted
执行取消之后,执行RealData的子线程就会被中断,然后结束任务。
怎么用wait、notify巧妙的设计一个Future模式?的更多相关文章
- 排查dubbo接口重复注销问题,我发现了一个巧妙的设计
背景 我在公司内负责自研的dubbo注册中心相关工作,群里经常接到业务方反馈dubbo接口注销报错.经排查,确定是同一个接口调用了两次注销接口导致,由于我们的注册中心注销接口不能重复调用,调用第二次会 ...
- 如何设计一个优雅健壮的Android WebView?(下)
转:如何设计一个优雅健壮的Android WebView?(下) 前言 在上文<如何设计一个优雅健壮的Android WebView?(上)>中,笔者分析了国内WebView的现状,以及在 ...
- 用Java如何设计一个阻塞队列,然后说说ArrayBlockingQueue和LinkedBlockingQueue
前言 用Java如何设计一个阻塞队列,这个问题是在面滴滴的时候被问到的.当时确实没回答好,只是说了用个List,然后消费者再用个死循环一直去监控list的是否有值,有值的话就处理List里面的内容.回 ...
- 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑
阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...
- 如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成
阅读目录 前言 建模 实现 结语 一.前言 前面几篇已经实现了一个基本的购买+售价计算的过程,这次再让售价丰满一些,增加一个会员价的概念.会员价在现在的主流电商中,是一个不大常见的模式,其带来的问题是 ...
- 如何一步一步用DDD设计一个电商网站(十)—— 一个完整的购物车
阅读目录 前言 回顾 梳理 实现 结语 一.前言 之前的文章中已经涉及到了购买商品加入购物车,购物车内购物项的金额计算等功能.本篇准备把剩下的购物车的基本概念一次处理完. 二.回顾 在动手之前我对之 ...
- 如何一步一步用DDD设计一个电商网站(七)—— 实现售价上下文
阅读目录 前言 明确业务细节 建模 实现 结语 一.前言 上一篇我们已经确立的购买上下文和销售上下文的交互方式,传送门在此:http://www.cnblogs.com/Zachary-Fan/p/D ...
- 如何一步一步用DDD设计一个电商网站(六)—— 给购物车加点料,集成售价上下文
阅读目录 前言 如何在一个项目中实现多个上下文的业务 售价上下文与购买上下文的集成 结语 一.前言 前几篇已经实现了一个最简单的购买过程,这次开始往这个过程中增加一些东西.比如促销.会员价等,在我们的 ...
- 如何一步一步用DDD设计一个电商网站(五)—— 停下脚步,重新出发
阅读目录 前言 单元测试 纠正错误,重新出发 结语 一.前言 实际编码已经写了2篇了,在这过程中非常感谢有听到观点不同的声音,借着这个契机,今天这篇就把大家提出的建议一个个的过一遍,重新整理,重新出发 ...
随机推荐
- numpy 其它常用方法
一.创建特殊的数组 1.ones() 语法 np.ones(shape, dtype=None) # shape 创建数组的shape # dtype 指定数组的数据类型 例子 import nump ...
- sqlalchemy 单表增删改查
1.连接数据库,并创建session from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine engi ...
- maven常用的远程仓库地址
<mirror> <id>nexus-aliyun</id> <name>Nexus aliyun</name> <url>ht ...
- [bzoj2186] [洛谷P2155] [Sdoi2008] 沙拉公主的困惑
Description 大富翁国因为通货膨胀,以及假钞泛滥,政府决定推出一项新的政策:现有钞票编号范围为1到N的阶乘,但是,政府只发行编号与M!互质的钞票.房地产第一大户沙拉公主决定预测一下大富翁国现 ...
- 最新中级java面试总结
最近面试了不少家公司,从一家非外包跳到了外包(委屈脸Ծ‸Ծ),下面总结一下最近的一些心得体会 1.如果在做技术只是为了过日子,而没多大兴趣,那你必须需要考虑一下行业发展方向了.选定一个行业,深入某个行 ...
- 深入学习MySQL 03 Schema与数据类型优化
Schema是什么鬼 schema就是数据库对象的集合,这个集合包含了各种对象如:表.视图.存储过程.索引等.为了区分不同的集合,就需要给不同的集合起不同的名字,默认情况下一个用户对应一个集合,用户的 ...
- 【javaScript】获取某年某月的的最后一天(即当月天数) 妙用
javaScript里 面的new Date("xxxx/xx/xx")这个日期的构造方法有一个妙处,当你传入的是"xxxx/xx/0"(0号)的话,得到的日期 ...
- ELK实战-elasticsearch安装
操作系统: centos版本 7.4 防火墙 关闭 selinux 关闭 elasticsearch版本 6.3.2 java版本 1.8 server1 192.168.10.126 server2 ...
- 这个时候 快下班了 我来翻译一段: Pro ASP.NET MVC 3 Framework
Binding to a Derived Type绑定派生类型Although we have focused on interfaces (since that is most relevant i ...
- HGE_improve 0.1发布
HGE_improve 0.1发布 写了1个月了,虽然还有很多很多缺陷,但丑姑娘也是要见公婆的. 主要修改如下: 1.全UNICODE化 2.增加切片动画 3.增加骨骼动画 4.增加MyGUI接口 5 ...