目录
保护性暂停-定义
Guarded Suspension,用一个线程等待另外一个线程的执行结果。
- 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject
- 如果有结果不断从一个线程到另外一个线程那么可以使用消息队列(生产者/消费者)
- JDK中,join的实现,Future的实现,采用的就是此模式
- 因为要等待另一方的结果,因此归类到同步模式
保护性暂停作为一种设计模式,其关键点在于GuardedObject,它有一个response的属性,t1想要获取它,需要等待,直到t2得到它时,将response返回给GuardedObject,再由GuardedObject返回给t1。
在这里需要用到线程 的wait()和notify()方法来完成代码编写。
保护性暂停-实现
编写一个GuardedObject类作为这样一个中间桥梁,成员变量 response。一个方法get()用于等待获得结果response,一个方法complete用于返回结果response,并叫醒正在等待的线程。
get()方法因为要等待response直到response不为空为止,如果为空就要继续等待。因此用了while条件判断来防止虚假唤醒,因为complete()唤醒线程的方式是notifyAll(),这样可能会导致虚假唤醒(比如唤醒以后并没有拿到response)
class GuardedObject {
private Object response;
public Object get() {
synchronized (this) {
//为了防止虚假唤醒 使用while条件判断 直到response不会空时才打破循环
while (response == null) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return response;
}
}
// 产生结果
public void complete(Object response) {
synchronized (this) {
// 给结果成员变量赋值
this.response = response;
// 得到结果 叫醒wait的线程
this.notifyAll();
}
}
}
编写两个线程,一个去等待response,一个去返回response。
t1通过guardedObject.get()方法的等待response返回,t2通过一个donwload方法得到一个list,将list作为response传递给guardedObject.complete()方法。
public static void main(String[] args) {
GuardedObject guardedObject = new GuardedObject();
new Thread(() -> {
log.debug("{}等待结果", Thread.currentThread().getName());
// 等待guardedObject的response
List<String> list = (List<String>) guardedObject.get();
log.debug("{}得到结果:{}", list.size());
}, "t1").start();
new Thread(() -> {
try {
log.debug("{}执行下载", Thread.currentThread().getName());
List<String> list = Downloader.download();
// 将下载结果返回给guardedObject的complate的方法
guardedObject.complete(list);
} catch (IOException e) {
e.printStackTrace();
}
}, "t2").start();
}
这就是在两个线程之间交互结果的模式,原来使用join()来交换结果,需要等待线程1执行结束。而保护性暂停可以让t2在执行下载以后还能干一些别的事,不一定要等t1结束,这也是join()方法的局限性,并且join()等待结果的变量必须是全局的,而不是像保护性暂停的模式都是局部变量。
保护性暂停-拓展-增加超时
在上面举例了保护性暂停的代码实现,线程t2用一个donwload()方法去得到一个list作为response传递给guardedObject.complete()。
假设donwload()有很慢的情况,也就是存在超时情况,t1线程就会一直循环等待,我们需要为超时的情况考虑。
目前 get() 方法只要没有拿到response,就会一直循环等待,需要为它增加一个超时判断。
为get()方法增加一个时间参数timeout,当超过这个时间,就不再等待。
但这个timeout是加在wait()方法上吗?假设这个超时时间是2秒,在wait(2)以后,仍然在循环中,因为response还是为null嘛,只是从一直等待变成了等2秒以后继续等待2秒+++++
因此超时时间要这样设置:
在等待之前,设计一个开始时间,在循环结束之前,设计一个经历时间。
// timeout 表示最大的等待时间(即超时时间)
public Object get(long timeout) {
long startTime = System.currentTimeMillis();
synchronized (this) {
// 经历的时间
long passtime = 0;
//为了防止虚假唤醒 使用while条件判断 直到response不会空时才打破循环
while (response == null) {
// 经历的时间大于等于超时时间 则跳出循环
if (passtime >= timeout) {
break;
}
try {
this.wait(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 开始时间 - 当前时间 = 经历的时间
passtime = startTime - System.currentTimeMillis();
}
return response;
}
}
假设最大的等待时间为2s,开始时间为15:00,一开始经历时间为0,在第一次循环结束的时候是15:02,因此经历的时间就是2s。
如果经历的时间大于等于最大的等待的时间,则跳出循环,当代码走到if判断时,经历时间等于了超时时间,条件成立,则跳出了循环。
还有一个很关键的问题就是,this.wait()方法里究竟放的是哪个时间参数?
刚刚假设的例子是2s,即timeout变量的值为2s,那么this.wait()是放入timeout的这个变量吗?就比如说,现在我们的经历时间是1s,走到if判断那里,条件不成立,那么还需要调用一次this.wait(),已经经历过1s了,虽然timeout的设定为2s,但是如果wait()里写的timeout这个变量,则又要等2s,这是不对的。正确的方式应该是 timeout - passtime ,this.wait()只需要再等待1s就好。
保护性暂停的模式理解起来并不难,但是在增加超时的拓展时,对于超时时间的设计让我觉得耳目一新,醍醐灌顶的感觉,因此写了博客记录学习笔记。