# 1.什么是保护性暂停模式
简单来说就是一个线程等待另一个线程的执行结果。JDK中join的实现、Future的实现、采用的就是此模式。因为要等待另一方的结果,因此归类到同步模式。
# 2.实现
public class GuardedObject {
private Object reponse;
// timeOut为获取结果最多等待的时间
public Object get(long timeOut){
synchronized (this){
//执行开始时间
long start=System.currentTimeMillis();
// 已经等待的时间 初始为0
long passedTime=0;
while (reponse==null) {
//这一轮循环需要等待的时间
long waitTime = timeOut - passedTime;
if (waitTime <= 0) {
break;
}
try {
// 这个地方为什么使用waitTime而不是timeOut?--为了防止虚假唤醒 例如我们假设最多等待时间为5秒。假如我们等待了2秒后发生了虚假唤醒(虚假唤醒就是我们还没有得到任务的结果。就被其他线程唤醒了) 这个时候如果我们这里wait等待时间设置为timeOut的话 在5秒之内还是没有得到结果被唤醒 我们就结束等待了。但是我们这里就一共等待了7秒。这显然和我们的设计不符。因此我们每次需要等待的时间一定要减去我们已经等待了的时间。
this.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
//整个流程所经历的时间---就是已经等待的时间
passedTime = System.currentTimeMillis() - start;
}
return reponse;
}
}
public void complete(Object reponse){
synchronized (this){
this.reponse=reponse;
this.notifyAll();
}
}
}
# 测试1.这里我们先模拟一下 t1执行2秒后得到任务结果。t2获取任务最多等待时间为3秒
public static void main(String[] args) {
GuardedObject guardedObject=new GuardedObject();
new Thread(()->{
try {
log.info("任务开始");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
guardedObject.complete(1);
log.info("任务结束");
},"t1").start();
new Thread(()->{
log.info("任务开始");
Object o = guardedObject.get(3000);
log.info("任务结束。获取任务结果"+o);
},"t2").start();
}
从结果我们可以看到当我们2秒后得到任务结果以后会立即返回。
# 测试2.这里我们先模拟一下 t1执行5秒后得到任务结果。t2获取任务最多等待时间为3秒
public static void main(String[] args) {
GuardedObject guardedObject=new GuardedObject();
new Thread(()->{
try {
log.info("任务开始");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
guardedObject.complete(1);
log.info("任务结束");
},"t1").start();
new Thread(()->{
log.info("任务开始");
Object o = guardedObject.get(3000);
log.info("任务结束。获取任务结果"+o);
},"t2").start();
}
从结果我们可以看到最多只等待了3秒。3秒过后即使我们还没有得到结果也返回了。
# 3.join原理
public final void join() throws InterruptedException {
join(0);
}
join方法其实就是调用了join(0);
// millis 就是我们要等待的时间
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
//isAlive()就是判断线程是否存活。简单来说就是如果线程没执行完就是存活状态。执行完了就返回false。 这块可以看到如果我们等待时间为0的话 直接调用不带参数的join()方法。millis就是等于0。其实就是调用的wait(0)方法,让线程一直等待下午。直到被其他线程唤醒。如果我们传入的millis不等于0 进入else。
while (isAlive()) {
wait(0);
}
} else {
其实我们可以看到这块的逻辑其实和我们上面的保护性暂停的模式是一样的。它的delay也就是我们每次还需要等待的时间。
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
# 简单小练习假设我们有三个信箱1,2,3每个家庭只对应一个信箱 通过我们有三个送信员 专门往三个信箱放信。这个时候三个家庭开始等待收信。信到了就打印信的内容
public class GuardedObject {
//标识 代表信箱id
private int id;
public GuardedObject(int id) {
this.id = id;
}
public int getId(){
return id;
}
// 代表 信息
private Object reponse;
// 取信操作
public Object get(long timeOut){
synchronized (this){
//执行开始时间
long start=System.currentTimeMillis();
// 已经等待的时间 初始为0
long passedTime=0;
while (reponse==null) {
//这一轮循环需要等待的时间
long waitTime = timeOut - passedTime;
if (waitTime <= 0) {
break;
}
try {
this.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
//整个流程所经历的时间---就是已经等待的时间
passedTime = System.currentTimeMillis() - start;
}
return reponse;
}
}
//送信操作
public void complete(Object reponse){
synchronized (this){
this.reponse=reponse;
this.notifyAll();
}
}
}
public class MailBoxes {
// 多线程情况下我们利用ConcurrentHashMap 来保存我们的信息
private static ConcurrentHashMap<Integer,GuardedObject> map=new ConcurrentHashMap<Integer,GuardedObject>();
private static int id=1;
/**
* 模拟一个id自增的情况 避免重现重复id;
* @return
*/
private static synchronized int generateId(){
return id++;
}
// 获取对应信箱id 的信息
public static GuardedObject getGuardedObject(int id){
return map.remove(id);
}
//创建信箱 将信箱id 和对应的信箱放入到map集合中
public static GuardedObject createGuardedObject(){
GuardedObject guardedObject=new GuardedObject(generateId());
map.put(guardedObject.getId(),guardedObject);
return guardedObject;
}
// 获取所有信箱id
public static Set<Integer> getIds(){
return map.keySet();
}
}
收信类
public class People implements Runnable {
@Override
public void run() {
// 收信 创建属于自己的信箱 等待收信
GuardedObject guardedObject = MailBoxes.createGuardedObject();
log.debug("开始收信 id:{}", guardedObject.getId());
Object mail = guardedObject.get(5000);
log.debug("收到信 id:{},信的内容 {}", guardedObject.getId(), mail);
}
}
送信类
public class PostMan implements Runnable{
private int id;
private String mail;
public PostMan(int id, String mail) {
this.id = id;
this.mail = mail;
}
@Override
public void run() {
// 往对应的信箱id 里面放信
GuardedObject guardedObject = MailBoxes.getGuardedObject(id);
log.debug("送信 id:{},内容:{}",id,mail);
guardedObject.complete(mail);
}
}
测试
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(new People()).start();
}
Thread.sleep(1000);
for (Integer id : MailBoxes.getIds()) {
new Thread(new PostMan(id, "内容" + id)).start();
}
}
结果打印
这个建议大家手写一遍MailBoxes我们可以当作一个工具类 而收信和送信其实就是实现类。这就是一个典型的保护者暂停模式的应用。