Future 模式有点类似于商品订单。比如在网购时,当看重某一件商品事,就可以提交 订单,当订单处理完成后,在家里等待商品送货上门即可。或者说更形象的我们发送 Ajax 请求的时候,页面是异步的进行后台处理,用户无须一直等待请求的结果,可以继续浏览或 操作其他内容。
代码1:Data接口
package com.bjsxt.chapter15;
/**
* @author wmr
* @date 2021/2/22
*/
public interface Data {
String getResult();
}
代码2:客户端Future
package com.bjsxt.chapter15;
/**
* @author wmr
* @date 2021/2/22
*/
public class FutureClient {
/**
* 提供给外界的查询接口
* @param queryStr 查询参数
* @return Data 类型的对象
*/
public Data search(String queryStr){
// 1.包装数据
final FutureData futureData = new FutureData();
// 2.另一个线程查询
new Thread(() -> {
RealData realData = new RealData(queryStr);
futureData.setRealData(realData);
}).start();
// 3.先返回一个结果
return futureData;
}
}
代码3:包装数据,包着真实数据引用变量
package com.bjsxt.chapter15;
/**
* 包装数据
* @author wmr
* @date 2021/2/22
*/
public class FutureData implements Data {
/**
* 真实数据的引用变量
*/
private RealData realData;
/**
* 是否已经放入真实数据
*/
private boolean isReady;
/**
* 真实数据放进包装数据对象中 ???
*/
public synchronized void setRealData(RealData realData){
// 如果已经放进去了,结束
if(isReady)
return;
// 否则真实数据放进包装数据,标记设置为true,唤醒因没有数据、而进入wait队列阻塞状态的线程
this.realData = realData;
this.isReady = true;
notify();
}
/**
* 获取结果
*/
@Override
public synchronized String getResult() {
if(!isReady){
try{
wait();
}catch (Exception e){
e.printStackTrace();
}
}
return this.realData.getResult();
}
}
代码4:真实数据
package com.bjsxt.chapter15;
import java.util.concurrent.TimeUnit;
/**
* @author wmr
* @date 2021/2/22
*/
public class RealData implements Data {
/**
* 结果
*/
private String result;
/**
* 查询数据 构造方法
*/
public RealData(String queryStr){
// 模拟查询时间5s,代表一个实际业务中的耗时操作
System.out.println("查询时长10s");
try{
TimeUnit.SECONDS.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
// 模拟结果 张三
this.result = "张三";
}
/**
* 获取结果
* @return
*/
@Override
public String getResult() {
return this.result;
}
}
代码5:模拟程序员调用这4个类的Future设计模式拿耗时间的查询数据
package com.bjsxt.chapter15;
/**
* Future设计模式(Data、FutureClient、FutureData、RealData)的测试类
*
* @author wmr
* @date 2021/2/22
*/
public class Test {
public static void main(String[] args) {
// main 线程
final FutureClient futureClient = new FutureClient();
Data data = futureClient.search("zhangsan");
String result = data.getResult();
System.out.println("result is "+result);
}
}
运行结果:
jstack查看main线程和Thread-0线程的java虚拟机栈:
main 线程由于调用getResult方法,包装数据类还未放入真实数据,陷入wait队列的阻塞状态
thread-0 线程模拟查询数据,进入普通的sleep 10秒的阻塞状态
分析:
Future设计模式在外面是怎么调用它的?这个设计模式怎么被使用的?遇见什么问题要是用这种方法?包装数据FutureData为什么要加上锁?还有这个写法:notify()跟过去的LOCK.notify()看起来不一样。
问题1:
在外面的程序员怎么调用Future设计模式?
回答1:
创建Future客户端对象,调用对象的查询方法,传入查询参数,获取返回的Data对象。从包装数据对象的获取结果方法拿结果使用。
public class Test {
public static void main(String[] args) {
// main 线程
final FutureClient futureClient = new FutureClient();
Data data = futureClient.search("zhangsan");
String result = data.getResult();
System.out.println("result is "+result);
// 使用结果做...
String other = result + "支付成功";
}
}
问题2:
客户端对象的查询方法是怎么写的?
回答2:
创建一个包装数据对象,返回包装数据对象给调用者。这个线程就结束了。
创建一个非守护线程,这个新的线程,创建一个真实数据对象,真实数据对象的构造方法执行获取真实数据的业务,拿到真实数据放在自己的result中;然后包装数据对象中真实数据引用变量指向这个真实数据。
public Data search(String queryStr){
// 1.包装数据
final FutureData futureData = new FutureData();
// 2.另一个线程查询
new Thread(() -> {
RealData realData = new RealData(queryStr);
futureData.setRealData(realData);
}).start();
// 3.先返回一个结果
return futureData;
}
问题3:
真实数据引用变量指向真实数据的方法为什么要加锁 synchronized 啊?
public synchronized void setRealData(RealData realData){
// 如果已经放进去了,结束
if(isReady)
return;
// 否则真实数据放进包装数据,标记设置为true,唤醒因没有数据、而进入wait队列阻塞状态的线程
this.realData = realData;
this.isReady = true;
notify();
}
回答3:
如果不加锁的尝试:
去掉 synchronized
public void setRealData(RealData realData){
// 如果已经放进去了,结束
if(isReady)
return;
// 否则真实数据放进包装数据,标记设置为true,唤醒因没有数据、而进入wait队列阻塞状态的线程
this.realData = realData;
this.isReady = true;
notify();
}
/**
* 获取结果
*/
@Override
public String getResult() {
if(!isReady){
try{
wait();
}catch (Exception e){
e.printStackTrace();
}
}
return this.realData.getResult();
}
运行结果:
忘了去掉notify和wait了,去掉后代码:
public void setRealData(RealData realData){
// 如果已经放进去了,结束
if(isReady)
return;
// 否则真实数据放进包装数据,标记设置为true,唤醒因没有数据、而进入wait队列阻塞状态的线程
this.realData = realData;
this.isReady = true;
// notify();
}
/**
* 获取结果
*/
@Override
public String getResult() {
// if(!isReady){
// try{
// wait();
// }catch (Exception e){
// e.printStackTrace();
// }
// }
return this.realData.getResult();
}
运行结果:
分析:
main 线程执行客户端的 search 方法,客户端返回包装数据对象,里面没有放进真实数据对象,没有对象调用对象方法,导致出现空指针异常。
总结:
使用这种模式可以使得调用设计模式的代码正常执行下面的业务代码,需要取数据时,如果引用变量指向了真实数据,那么返回真实数据的result值,否则线程陷入wait队列的阻塞状态。等待其他线程唤醒。设置引用变量指向真实数据的方法,如果已经指向了,那么结束,否则蒋引用变量指向真实数据对象,isReady设置为true,唤醒陷入wait队列的阻塞线程进入锁池,竞争到包装数据对象this锁后进入runnable状态,获取cpu执行时间片,执行,通过获取包装数据结果。而不会出现空指针异常。
重点是包装数据的设置真实数据引用变量,还有获取真实数据的result值方法。外部使用Future客户端的调用search方法获取Data包装数据对象。获取包装对象中result方法的调用。如果还没有连上真实数据对象,这个线程阻塞。等待赋值完毕的线程的唤醒。