多线程设计模式-保护性暂停之面向对象

发布于:2024-12-20 ⋅ 阅读:(8) ⋅ 点赞:(0)

保护性暂停模式,也是多线程的一种固定模式,使用着这种模式时候,当线程在访问某个对象时,发现条件不满足时,就暂时挂起等待条件满足时再次访问。

并且能够防止虚假唤醒

那我们不禁要思考,在多线程终止模式的时候,用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();
        }
    }

得到结果如下