保护性暂停模式,也是多线程的一种固定模式,使用着这种模式时候,当线程在访问某个对象时,发现条件不满足时,就暂时挂起等待条件满足时再次访问。
并且能够防止虚假唤醒
那我们不禁要思考,在多线程终止模式的时候,用interrupt或者一个共享变量作为标记位,来进行优雅的终止,
在 保护性暂停的时候呢,也是 传入一个由线程定义的时间变量,发现条件不满足,进入 等待,即使其中被虚假唤醒,只要条件不满足,我们仍进入挂起状态,直到设置的等待时间结束
1.一直等待条件满足的 情况 代码
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;
}
}
线程操作实例
package thread;
// 同步模式 保护性暂停
public class ThreadTest4GuardedSuspension {
public static void main(String[] args) {
long start = System.currentTimeMillis();
GuardedSuspension object = new GuardedSuspension();
new Thread(new Runnable() {
@Override
public 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() {
@Override
public 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.带有超时时间的保护性暂停代码
其实就是 在原有的基础上加了一个重载的方法
public synchronized Object getObject(Long time) throws InterruptedException {
//超时等待 必须要让 该线程 等够 一定的时间
long start = System.currentTimeMillis();//开始时间
long passTime=0;
// 4 s
long 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 类
这样的话虽然可以实现,但是 整体的拓展性,代码的健壮性是不够友好的。
这个时候可以用面向对象的思维
我们把 等待资源的线程抽成一个对象,放置资源的线程抽成一个对象
用一个中间的类 ,来联系 这两个对象,然后 让 主线程 去创建对象去操作,这样代码的扩展性能也是更优解
代码实现
class Postman extends Thread {
private int id;
private String message;//信件内容
public Postman(int id, String mail) {
this.id = id;
this.message = mail;
}
@Override
public 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对象,保证每个线程 资源对象的唯一性
在主线程中运行为
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();
}
}
得到结果如下
可