保护性暂停模式,也是多线程的一种固定模式,使用着这种模式时候,当线程在访问某个对象时,发现条件不满足时,就暂时挂起等待条件满足时再次访问。
并且能够防止虚假唤醒
那我们不禁要思考,在多线程终止模式的时候,用interrupt或者一个共享变量作为标记位,来进行优雅的终止,
在 保护性暂停的时候呢,也是 传入一个由线程定义的时间变量,发现条件不满足,进入 等待,即使其中被虚假唤醒,只要条件不满足,我们仍进入挂起状态,直到设置的等待时间结束
1.一直等待条件满足的 情况 代码
java">public class GuardedSuspension{private Integer id;public GuardedSuspension(Integer id){this.id=id;}public GuardedSuspension(){}public Integer getId(){return this.id;}private Object guardedSuspensionObject;public synchronized void setObject(Object object) throws InterruptedException {this.guardedSuspensionObject=object;notifyAll();}public synchronized Object getObject() throws InterruptedException {while (this.guardedSuspensionObject==null){System.out.println("调用了wait");wait();}return guardedSuspensionObject;}
}
线程操作实例
java">package thread;
// 同步模式 保护性暂停
public class ThreadTest4GuardedSuspension {public static void main(String[] args) {long start = System.currentTimeMillis();GuardedSuspension object = new GuardedSuspension();new Thread(new Runnable() {@Overridepublic void run() {try {Object object1 = object.getObject();if(object1==null){System.out.println("没有东西");}else{System.out.println("获得了");}} catch (InterruptedException e) {throw new RuntimeException(e);}}},"t1").start();try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}new Thread(new Runnable() {@Overridepublic void run() {try {object.setObject(new Object());System.out.println("放置了");long end= System.currentTimeMillis();System.out.println("线程t1经过的时间为"+(start-end));} catch (InterruptedException e) {throw new RuntimeException(e);}}},"t2").start();}
}
当我们t1线程 一直获取不到 object对象的时候,t1会一直等待,直到一秒后 t2运行在同一个类中放置,然后t1 被唤醒,再次运行
2.带有超时时间的保护性暂停代码
其实就是 在原有的基础上加了一个重载的方法
java"> public synchronized Object getObject(Long time) throws InterruptedException {//超时等待 必须要让 该线程 等够 一定的时间long start = System.currentTimeMillis();//开始时间long passTime=0;// 4 slong waitTime=time-passTime;while (guardedSuspensionObject==null||waitTime>0){waitTime=time-passTime;//上面三行 代码 都可以根据自身使用情况 来自由 变换逻辑 ,如果以//超时时间为主,那么可以不用改变,如果以 是否 暂停 停止等待的条件为主//可以改成如下// while (guardedSuspensionObject==null){// long waitTime=time-passTime;if(waitTime<=0){System.out.println("等待时间到了");break;}wait(waitTime);//2s 被唤醒了passTime=System.currentTimeMillis()-start;}return guardedSuspensionObject;}
,当我们被唤醒的时候, 即使 线程满足 停止等待的条件,但是等待时间没到 他还是要休眠 , 或者 可以把while 改成,只要 线程满足 停止等待的条件 ,无论等待时间是否到没到,都停止等待,这个主要 在于 使用者 更希望是 等待时间的优先级更高还是 暂停等待的优先级更高来设置
而精华部分 ,就是每次被唤醒的时候,对于 重置等待时间的处理
面向对象思维
我们 上面对于超时等待的处理 是 一个线程 等待 另外一个 线程的 资源放置
那么 如果是 假如有3 个线程等待 另外三个线程放置 object , 这个时候就会 变成 三个线程中的 一对一
那么 我们先不看代码,而是想想这个代码要怎么写
思考
写的话就会刚开始想到,我for 三个线程出来,在for 三个发放置资源的线程,在for三个 中间的
GuardedSuspension 类
这样的话虽然可以实现,但是 整体的拓展性,代码的健壮性是不够友好的。
这个时候可以用面向对象的思维
我们把 等待资源的线程抽成一个对象,放置资源的线程抽成一个对象
用一个中间的类 ,来联系 这两个对象,然后 让 主线程 去创建对象去操作,这样代码的扩展性能也是更优解
代码实现
java">class Postman extends Thread {private int id;private String message;//信件内容public Postman(int id, String mail) {this.id = id;this.message = mail;}@Overridepublic void run() {GuardedSuspension guardedSuspension = MailBox.getGuardedSuspension(id);System.out.println("开始送信了内容为"+message);try {guardedSuspension.setObject(message);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
class MailBox {private static final ConcurrentHashMap<Integer, GuardedSuspension> concurrentHashMap= new ConcurrentHashMap<>();private static Integer id = 1;private static synchronized int generateId() {return id++;}public static GuardedSuspension createGuardedSuspension() {GuardedSuspension guardedSuspension = new GuardedSuspension(generateId());concurrentHashMap.put(guardedSuspension.getId(), guardedSuspension);return guardedSuspension;}public static GuardedSuspension getGuardedSuspension(Integer id) {return concurrentHashMap.remove(id);}public static Set<Integer> getIds(){ConcurrentHashMap.KeySetView<Integer, GuardedSuspension> integers = concurrentHashMap.keySet();return integers;}
上面代码中,我们用concurrenthashmap来保证线程安全,用一个单例的 map对象,保证每个线程 资源对象的唯一性
在主线程中运行为
java"> public static void main(String[] args) throws InterruptedException {for (int i = 0; i <3 ; i++) {People people = new People();people.start();}Thread.sleep(1000);for (Integer id : MailBox.getIds()) {new Postman(id,"发送者id是"+id).start();}}
得到结果如下
可