网上讲解该设计模式的文章非常地多,很多讲的内容、技术都比我全面和深入!但既然学习,我还是想进行一下总结!希望阅读本文章的你可以获得一点启发!
说到并发编程,我们都会觉得,哇,并发编程是真的难!可事实真的是那样子吗?我觉得不是,一门技术出来,只要你有那个决心去攻克它,那么你就赢了。也就是我们常说的:“稳住,猥琐发育,别浪,咱能赢!”,学习的过程中肯定会遇到一些难点,多找人沟通交流,相互进步是非常好的途径,小编希望你们在阅读我总结的文章之后能和小编一起交流,不甚荣幸。
一、什么是异步?
讲解设计模式之前我先讲下异步,异步和同步是对立的,这两个概念其实是不难理解的,关键是同步和异步究竟是怎样在程序中体现,才是我们最好奇,最想知道的。
同步
:就是当任务A依赖于任务B的执行时,必须等待任务B执行完毕之后任务A才继续执行,此过程任务A被阻塞。任务要么都成功,要么都失败!想一想我们打电话的情景即可! 异步
:任务A调用任务B,任务A不需要等到任务B执行完毕,任务B只是返回一个虚拟的结果给任务A,使得任务A能够继续做其他事情,等到任务B执行完成之后再通知任务A(回调)或者是任务A主动去请求任务B要结果。想一想发短信的情景即可!
二、Future
异步设计模式
首先我在网上找到了一张图,分别对比了异步
和同步
的时序区别:
那么我们怎么实现呢?先贴一张UML图再说:
讲解实例之前,我觉得有必要说一些先决条件:
- 1、
notify/wait
的使用,每个java对象都拥有这两个方法,调用这两个方法必须先获得监视器,常用的做法是在synchronized
同步块中进行调用 - 2、
生产者/消费者
模型,其实这个可有可无,主要是不要写出在if(块中)
调用notify/wait
方法,而要改为while
我所有的逻辑注释都穿插在下面的代码中,如果有想要一起交流的问题,欢迎评论,欢迎评论,欢迎评论!
Main.java
package com.wokao66.future;
/**
* 持有一个客户端Client对象,发送请求
* @author: huangjiawei
* @since: 2018年4月2日
* @version: $Revision$ $Date$ $LastChangedBy$
*/
public class Main {
public static void main(String[] args) throws Exception {
/**
* 持有一个客户端Client对象
*/
Client client = new Client();
/**
* 返回一个虚拟的数据(这是异步返回的,虚拟的,不是真实的,但必须持有真实数据对象realData,方便后面获取请求结果)
*/
Data virtualData = client.request("我要下单!!!!");
/**
* 睡眠5秒
*/
try {
Thread.sleep(5000);
} catch (InterruptedException e) {}
/**
* 我现在想获得真实数据了
*/
String realData = virtualData.getResultData();
System.err.println("真实数据为:" + realData);
}
}
复制代码
Data.java
package com.wokao66.future;
/**
* 对返回数据的简单抽象(虚拟数据和真实数据都必须实现该接口)
* @author: huangjiawei
* @since: 2018年4月2日
* @version: $Revision$ $Date$ $LastChangedBy$
*/
public interface Data {
/**
* 获取数据的操作,至于是虚拟的,还是真实的,我不用管,让实现类去决定
* @throws Exception
*/
public abstract String getResultData() throws Exception;
}
复制代码
VirtualData.java
package com.wokao66.future;
/**
* 虚拟的数据(异步返回给客户端)
* @author: huangjiawei
* @since: 2018年4月2日
* @version: $Revision$ $Date$ $LastChangedBy$
*/
public class VirtualData implements Data {
/**
* 想象一下,如果你这里返回的VirtualData不包含对RealData的引用,那么当客户端需要获取真实数据时,你的数据从何而来???
*/
private RealData realData = null;
/**
* 默认是还没有准备好数据嘛!要有个状态来跟踪
*/
private boolean isReady = false;
/**
* 注入RealData,这个RealData
* @param realData
*/
public synchronized void setRealData(RealData realData) {
System.err.println("获得锁");
/**
* 如果还没有准备好,我就需要
*/
if (isReady) {
return;
}
this.realData = realData;
isReady = true;//我已经准备好了
notify();//通知所有阻塞的线程
}
/**
* 重写获取数据的方式
* @throws Exception
*/
@Override
public synchronized String getResultData() throws Exception {
/**
* 如果客户端调用的时候我还没有注入真实数据,那么就一直阻塞
*/
while (!isReady) {
//调用wait必须先获得对象的锁,所以
wait();
}
return realData.getResultData();
}
}
复制代码
RealData.java
package com.wokao66.future;
/**
* 这个类表示你具体的业务操作,比如重数据库查询数据
* @author: huangjiawei
* @since: 2018年4月2日
* @version: $Revision$ $Date$ $LastChangedBy$
*/
public class RealData implements Data {
/**
* 请求名
*/
private String readData;
public RealData(String readData) {
/**
* 我这里先休眠10秒,表示一个耗时的操作
*/
try {
Thread.sleep(10000);
} catch (InterruptedException e) {}
this.readData = "调用名为 : " + readData + " , " + "真实的数据为 : realData";
}
@Override
public String getResultData() {
return readData;
}
}
复制代码
Client.java
package com.wokao66.future;
/**
* 表示我们的客户端程序嘛!负责发起调用请求
* @author: huangjiawei
* @since: 2018年4月2日
* @version: $Revision$ $Date$ $LastChangedBy$
*/
public class Client {
/**
* 表示客户端的请求
* @param name 具体的请求名称
* @return
*/
public Data request(String name) {
/**
* 声明一个虚拟的数据
*/
VirtualData virtualData = new VirtualData();
/**
* 当你调用请求时,我后台默默开启一个线程去处理真实操作
*/
new Thread(new Runnable() {
@Override
public void run() {
System.err.println("我偷偷摸摸地请求后台获取数据的操作,该操作可能会执行很长的时间");
System.err.println("我不管了,先返回结果给调用方");
RealData realData = new RealData(name);
//下面两句的执行顺序是不一样的
//启动线程
virtualData.setRealData(realData);
}
}).start();
//我先返回一个虚拟的数据给你,真的数据等我获取完成之后你再过来取
return virtualData;
}
}
复制代码
运行结果
下单成功...............
我偷偷摸摸地请求后台获取数据的操作,该操作可能会执行很长的时间
我不管了,先返回结果给调用方
获得锁
真实数据为:调用名为 : 我要下单!!!! , 真实的数据为 : realData
复制代码
不知道大家有没有思考过,上面Main.java
中String realData = virtualData.getResultData();
是一个本地对象,只能在本机执行,那如果是基于tcp的网络传输呢?可以采用个推,websocket等方式进行,dubbo
目测也是可以的!对了,突然想起来,Java EE 7
开始已经支持异步Servlet
了,详细的可以自行查看相关文档!
三、Java 中的异步并发编程
异步执行结果无非就有两种形式:
- 1、执行之后有回调
- 2、执行之后没有回调
有回调
指的是客户端建立相应的监听器Listener
,服务端执行异步Future
之后主动回调客户端的回调函数
无回调
即我们前面例子讲的,我们需要自己主动去请求服务端判断是否已经成功了
package com.wokao66.javafuture;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test {
public static void main(String[] args) {
/**
* 新建一个线程池
*/
ExecutorService executor = Executors.newFixedThreadPool(1);
/**
* 创建一个任务
*/
Task task = new Task();
/**
* 将任务提交给线程池
*/
Future<Integer> result = executor.submit(task);
//这里会保证所有子线程执行完毕再关闭
executor.shutdown();
try {
/**
* 模拟执行其它操作
*/
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("我在执行其他操作........");
try {
System.out.println("任务的执行结果是:" + result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class Task implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("子线程在进行计算");
/**
* 模拟执行耗时操作
*/
Thread.sleep(10000);
return 100;
}
}
复制代码
执行结果
子线程在进行计算
我在执行其他操作........
任务的执行结果是:100
复制代码
同时java也可以使用FutureTask
来创建任务,FutureTask
同时实现Runnable
和Callable
接口,非常方便!
谢谢阅读!