同步模式之保护性暂停
同步模式之保护性暂停
定义
Guarded Suspension,用在一个线程等待另一个线程的执行结果
要点
· 有一个结果需要从一个线程传递到另一个线程,让它们关联同一个GuardedObject
· 如果有结果不断从一个线程到另一个线程那么可以使用消息队列
· JDK中,join的实现、Future的实现,采用的就是此模式
· 因为要等待另一方结果,因此归类到同步模式
实现
@Slf4j(topic = "c.GuardedObject")
public class GuardedObject {//线程1等待线程2的下载结果public static void main(String[] args) {MyTest myTest = new MyTest();new Thread(()->{//等待结果log.debug("等待结果");Object res = (Date)myTest.get();log.debug("结果是:{}",res);},"t1").start();new Thread(()->{log.debug("执行下载");try {Thread.sleep(10000);myTest.complete(new Date());} catch (InterruptedException e) {e.printStackTrace();}},"t2").start();}}class MyTest{// 结果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();}}
}
优点
相较于join来说,join只能等另一个线程结束,而这种模式在唤醒等待线程后还可以让当前线程继续执行别的代码。而且使用join时,等待结果的变量只能设计成全局的,而这种设计方法可以将变量设置成局部的。
扩展1-增加超时效果
@Slf4j(topic = "c.GuardedObject")
public class GuardedObject {//线程1等待线程2的下载结果public static void main(String[] args) {MyTest myTest = new MyTest();new Thread(()->{//等待结果log.debug("等待结果");Object res = (Date)myTest.get(5000);log.debug("结果是:{}",res);},"t1").start();new Thread(()->{log.debug("执行下载");try {Thread.sleep(10000);myTest.complete(new Date());} catch (InterruptedException e) {e.printStackTrace();}},"t2").start();}}class MyTest{// 结果private Object response;//获取结果的方法public Object get(long timeout){synchronized (this){//开始时间long begin = System.currentTimeMillis();//经历的时间long passedTime = 0;while (response == null){//等待时间long waitTime = timeout-passedTime;if(waitTime<=0){break;}//经历的时间超过了最大等待时间,退出循环if(passedTime >= timeout){break;}try {//避免虚假唤醒使再次等待的时间变长this.wait(waitTime);} catch (InterruptedException e) {e.printStackTrace();}//求得经历时间passedTime = System.currentTimeMillis() - begin;}return response;}}//产生结果public void complete(Object response){synchronized (this){//给结果成员变量赋值this.response = response;this.notifyAll();}}
}
join原理
join底层采用了保护性暂停模式
扩展2
图中Futures就好比居民楼一层的信箱(每个信箱有房间编号),左侧的t0,t2,t4就好比等待邮件的居民,右侧的t1,t3,t5就好比邮递员
如果需要在多个类之间使用GuardedObject对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类,这样不仅能够解耦结果等待者和结果生产者,还能够同时支持多个任务的管理
实现代码
@Slf4j(topic = "c.GuardedObject")
public class GuardedObject {public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 3; i++) {new People().start();}Thread.sleep(1000);for (Integer id : Mailboxes.getIds()) {new Postman(id,"内容"+id).start();}}
}@Slf4j(topic = "c.People")
class People extends Thread{@Overridepublic void run() {// 收信MyTest guardedObject = Mailboxes.createGuardedObject();log.debug("开始收信 id:{}",guardedObject.getId());Object mail = guardedObject.get(5000);log.debug("收到信 id:{},内容:{}",guardedObject.getId(),mail);}
}@Slf4j(topic = "c.Postman")
class Postman extends Thread{private int id;private String mail;public Postman(int id,String mail){this.id = id;this.mail = mail;}@Overridepublic void run() {MyTest guardedObject = Mailboxes.getGuardedObject(id);log.debug("开始送信 id:{},内容{}",id,mail);guardedObject.complete(mail);}
}class Mailboxes{private static Map<Integer,MyTest> boxes = new Hashtable<>();private static int id = 1;//产生唯一idprivate static synchronized int generateId(){return id++;}public static MyTest getGuardedObject(int id){return boxes.remove(id);}public static MyTest createGuardedObject(){MyTest go = new MyTest(generateId());boxes.put(go.getId(),go);return go;}public static Set<Integer> getIds(){return boxes.keySet();}
}class MyTest{// 标识 Guarded Objectprivate int id;public MyTest(int id) {this.id = id;}public int getId() {return id;}// 结果private Object response;//获取结果的方法public Object get(long timeout){synchronized (this){//开始时间long begin = System.currentTimeMillis();//经历的时间long passedTime = 0;while (response == null){//等待时间long waitTime = timeout-passedTime;if(waitTime<=0){break;}//经历的时间超过了最大等待时间,退出循环if(passedTime >= timeout){break;}try {//避免虚假唤醒使再次等待的时间变长this.wait(waitTime);} catch (InterruptedException e) {e.printStackTrace();}//求得经历时间passedTime = System.currentTimeMillis() - begin;}return response;}}//产生结果public void complete(Object response){synchronized (this){//给结果成员变量赋值this.response = response;this.notifyAll();}}
}