目录
保护性暂停设计模式
获取结果
产生结果
总代码实现
测试
增加超时效果的Guarded suspension
get(long timeout)
测试
保护性暂停设计模式
Guarded Suspension 即 保护性暂停; 是一种等待唤醒机制的一种规范 ,也可以理解为使用中设计模式,Java的API很多都按照保护性暂停这种设计模式来的,比如 join,future.
保护性暂停设计模式(Guarded Suspension) ,一般适用于 一个线程等待另外一个线程的执行结果,两个线程一一对应,可以划分为同步的设计模式.
如果你需要一个结果要源源不断的从一个线程到另外一个线程,那就需要使用生产者消费者模式->即消息队列.
保护性暂停设计模式 也是使用到wait和notify这种等待唤醒机制实现的,接下来我给一下它的代码实现:
获取结果
//获取结果
public Object get(){
synchronized (this){
while(response==null){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return response;
}
}
还是按照wait和notify使用来实现, 如果一直没有给response赋值,它就一直等着,如果另外一个线程给response赋值,就立即返回这个结果.
产生结果
//传入/产生 结果
public void complete(Object response){
synchronized (this) {
this.response = response;
this.notifyAll();
}
}
等到一个线程传入结果即为response赋值,那么就立即给response赋值,然后通知正在等待的线程,唤醒他,让其返回结果.
总代码实现
我们利用GuardedObject来封装这两个方法,关联同一个GuardedObject
/**
* Description: 保护性暂停模式
*/
public class GuardedObject {
private Object response;
//获取结果
public Object get(){
synchronized (this){
while(response==null){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return response;
}
}
//传入/产生 结果
public void complete(Object response){
synchronized (this) {
this.response = response;
this.notifyAll();
}
}
}
测试
@Slf4j(topic = "c.Main")
public class Main {
public static void main(String[] args) {
GuardedObject guardedObject = new GuardedObject();
//A线程等待B线程的结果
new Thread(()->{
List<String> list = (List<String>) guardedObject.get();
log.debug("得到结果为");
System.out.println(list);
},"A").start();
new Thread(()->{
try {
List<String> list = DownLoader.downLoad();
guardedObject.complete(list);
} catch (IOException e) {
e.printStackTrace();
}
},"B").start();
}
}
public class DownLoader {
public static List<String> downLoad() throws IOException {
//下载百度首页
HttpURLConnection connection = (HttpURLConnection) new URL("https://www.baidu.com/").openConnection();
List<String> lines = new ArrayList<>();
try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while((line= bufferedReader.readLine()) != null) {
lines.add(line);
}
}
return lines;
}
}
写了一个测试类,用来对保护性暂停设计模式进行测试.一个线程A用来接收下载结果,另外一个线程B执行完下载(DownLoader类用于下载百度首页)之后,将下载结果传入,线程A就可以获取到结果然后进行结果的打印.
增加超时效果的Guarded suspension
主要在get()方法上增加超时效果,获取结果的时候,不需要一直等了,可以选择性的等几秒,如果等不到就不等了.
get(long timeout)
为了更好地理解,我先一步一步的改代码,最终形成最终版本
如果想要等待timeout时间,就是等待足够timeout,不能多也不能少.必须整好.
一个问题直接加上Timeout行不行
private Object response;
//获取结果
public Object get(long timeout){
synchronized (this){
while(response==null){
try {
this.wait(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return response;
}
}
答案是不行的,就算等完2s之后还要再次进入while循环,条件还是不成立,又要等待2s,这就相当于每2s等待一次,也就相当于一直等待
所以我们要琢磨一下:
首先最开始刚获取结果的时候就加一个开始时间-->用于记录刚要获取结果的时间,然后等待往后推2s.
long begin = System.currentTimeMillis();//记录刚开始获取结果的时间->刚获取到锁
还必须要一个passTime 表示经历的时间
long passTime = 0;//表示经历的时间,刚开始为0ms
那怎么记录passTime呢 ?
我们需要每轮等待一次之后进行获取->用当时的时间-开始的时间就是
passTime = System.currentTimeMillis() - begin;
当passTime>=timeout 就代表等到了足够时间还没有结果,就不等了立即 break,退出循环不等了.
if(passTime>=timeout){
break;
}
现在就差wait部分的时间没有填写.
- 填timeout行不行?
答案是不行的,还要考虑虚假唤醒的情况就比如它在00:00s开始获取结果,timeout=2s,在00:01s时候被唤醒,求得经历时间为1s,再次循环之后passTime>=timeout不成立,然后就会接着在等timeout(2s),但是按理说我们再等待1s就可以了,这样的话你就多等了.
我们等待timeout-passTime就是正确的,这才是一轮需要等待的时间;
于是可以写如下代码:
//获取结果
public Object get(long timeout){
synchronized (this){
long begin = System.currentTimeMillis();
long passTime = 0;
while(response==null){
if(passTime>=timeout){
break;
}
try {
this.wait(timeout-passTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
passTime = System.currentTimeMillis() - begin;
}
return response;
}
}
接着再精简一下给出整体代码:
public class GuardedObject {
private Object response;
//获取结果
public Object get(long timeout){
synchronized (this){
long begin = System.currentTimeMillis();
long passTime = 0;
while(response==null){
//应该等待的时间
long waitTime = timeout - passTime;
if(waitTime<=0){
break;
}
try {
this.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
passTime = System.currentTimeMillis() - begin;
}
return response;
}
}
//传入/产生 结果
public void complete(Object response){
synchronized (this) {
this.response = response;
this.notifyAll();
}
}
}
测试
测试提前传入结果:
测试超时传入结果:
测试虚假唤醒的情况