5、设计模式——同步模式之保护性暂停、join的原理、异步模式之生产者消费者模式

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

上一章https://blog.csdn.net/m0_45364328/article/details/124936454
我们介绍了wait/notify的原理,为加深理解,我们本章介绍一下,由wait/notify延申出的设计模式


保护性暂停的定义

用一个线程等待另一个线程的执行结果
要点:
1、有一个结果要从一个线程传递到另一个线程,让它们关联同一个GuadedObject
2、如果有结果不断地从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
3、JDK中join的实现、Future的实现,采用的就是此模式
因为要对等待另一方的结果,因此归类到同步模式
在这里插入图片描述

简单实现

@Slf4j(topic = "c.Test20")
public class Test20 {

    //线程1等待线程2的执行结果
    public static void main(String[] args) {
        GuadenObject guadenObject = new GuadenObject();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Object o = guadenObject.get();
                System.out.println(o);
            }
        },"t1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                //执行任务
                try {
                    Thread.sleep(3000);
                    String result = "结果";
                    guadenObject.complete(result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t2").start();


    }
}

class  GuadenObject{
    //结果
    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();
        }
    }
}
结果

增加超时等待

 //超时
    public Object get(long timeout){
        synchronized (this){
            //在进入循环等待前首先记录一下开始时间
            long begin = System.currentTimeMillis();
            long passedTime=0;
            while (response==null){
                if (passedTime>=timeout){//如果已经等待的时间超过了timeout就退出循环
                    break;
                }
                try {
                    this.wait(timeout-passedTime);   //timeout-passedTime,还需要再等多少时间,解决虚假唤醒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //求得已经等待了多少时
                passedTime= System.currentTimeMillis()-begin; 
            }
            return response;
        }
    }

关于this.wait(timeout-passedTime)做一个说明:

  //线程1等待线程2的执行结果
    public static void main(String[] args) {
        GuadenObject guadenObject = new GuadenObject();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Object o = guadenObject.get();
                System.out.println(o);
            }
        },"t1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                //执行任务
                try {
                    Thread.sleep(3000);
                    guadenObject.complete(null);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t2").start();


    }
}

线程t2,执行complete给结果response赋值的时候,如果赋值的是null,也就说没有结果,唤醒t1,这个虚假唤醒
如果是this.wait(timeout)

           while (response==null){
                if (passedTime>=timeout){//如果已经等待的时间超过了timeout就退出循环
                    break;
                }
                try {
                    this.wait(timeout);   
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //求得已经等待了多少时
                passedTime= System.currentTimeMillis()-begin; 
            }
            return response;
        }

那么线程t1,就还要再等待timeout时间,那已经等待的passedTime就白等了,因此timeout-passedTime称为还需要等待的时间,就是解决上述虚假唤醒的状况。

join的原理

join的原理就是保护性暂停模式
下面看下join()的源码

public final void join() throws InterruptedException {
        join(0);
    }
 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) {//只要线程还活着就一直等待
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {//只要线程活着(没有执行完),就循环等待
                long delay = millis - now;//还需要等待的时间
                if (delay <= 0) {不需要等待了,就跳出循环
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;//已经等待了的时间
            }
        }
    }

扩展——保护性暂停模式的应用

在这里插入图片描述


@Slf4j(topic = "c.Test20")
public class Test20 {


    public static void main(String[] args) throws Exception {
        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 {
    @Override
    public void run() {
        //收信
        GuadenObject guadenObject = Mailboxes.createGuadenObject();//一个用户对应一个邮箱中的格子,一家一户
        log.debug("开始收信 id:{}", guadenObject.getId());
        Object mail = guadenObject.get();
        log.debug("收到信 id:{},内容:{}", guadenObject.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;
    }
    @Override
    public void run() {
        GuadenObject guadenObject = Mailboxes.getGuadenObject(id);
        log.debug("送信 id:{},内容:{}", id, mail);
        guadenObject.complete(mail);

    }
}

/**
 * 这个类相当于邮箱类
 */
@Slf4j(topic = "c.Mailboxes")
class Mailboxes {
    private static Map<Integer, GuadenObject> boxes = new HashMap<>();

    private static int id = 1;

    public static GuadenObject getGuadenObject(int id) {
        return boxes.remove(id);
    }

    //产生唯一id
    private static synchronized int generateId() {
        return id++;
    }

    public static GuadenObject createGuadenObject() {
        GuadenObject guadenObject = new GuadenObject(generateId());
        boxes.put(guadenObject.getId(), guadenObject);
        return guadenObject;
    }

    public static Set<Integer> getids() {
        return boxes.keySet();
    }
}


/**
 * 这个类就相当于邮箱中的格子类
 */
@Slf4j(topic = "c.GuadenObject")
class GuadenObject {
    //标识 Guaden Object
    private int id;

    public GuadenObject(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) {
                if (passedTime >= timeout) {//如果已经等待的时间超过了timeout就退出循环
                    break;
                }
                try {
                    this.wait(timeout - passedTime);   //timeout-passedTime,还需要再等多少时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //求得已经等待了多少时
                passedTime = System.currentTimeMillis() - begin;
            }
            return 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();
        }
    }
}
01:35:20.400 [Thread-1] DEBUG c.People - 开始收信 id:1
01:35:20.401 [Thread-0] DEBUG c.People - 开始收信 id:2
01:35:20.402 [Thread-2] DEBUG c.People - 开始收信 id:3
01:35:21.403 [Thread-3] DEBUG c.PostMan - 送信 id:1,内容:内容1
01:35:21.403 [Thread-1] DEBUG c.People - 收到信 id:1,内容:内容1
01:35:21.403 [Thread-4] DEBUG c.PostMan - 送信 id:2,内容:内容2
01:35:21.403 [Thread-0] DEBUG c.People - 收到信 id:2,内容:内容2
01:35:21.404 [Thread-5] DEBUG c.PostMan - 送信 id:3,内容:内容3
01:35:21.404 [Thread-2] DEBUG c.People - 收到信 id:3,内容:内容3

由此可以看出保护性暂停模式是一种一对一的模式,是很多RPC框架的底层,使用Mailboxes 解耦结果的产生者和消费者

异步模式之生产者消费者模式

在这里插入图片描述

@Slf4j(topic = "c.Message")
public class Test21 {
    public static void main(String[] args) {
        MessageQueue queue = new MessageQueue(2);
        for (int i=0;i<3;i++){
            int id=i;
            new Thread(()->{
                queue.put(new Message(id,"值"+id));
            },"生产者"+id).start();
        }
        new Thread(()->{
            while (true){
                try {
                    Thread.sleep(1000);
                    Message message = queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

@Slf4j(topic = "c.Message")
class MessageQueue {
    //消息队列集合
    private LinkedList<Message> list = new LinkedList();
    //队列容量
    private int capcity;

    public MessageQueue(int capcity) {
        this.capcity = capcity;
    }

    //获取消息
    public Message take() {
        synchronized (list) {
            while (list.isEmpty()) {
                log.debug("队列为空,消费者线程等待");
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //从队列的头部获取消息返回
            Message message = list.removeFirst();
            log.debug("已消费消息{}"+message);
            list.notifyAll();
            return message;
        }


    }

    public void put(Message message) {
        synchronized (list) {
            //检查队列是否已满
            while (list.size() == capcity) {
                log.debug("队列已满,生成者线程等待");
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            list.addLast(message);
            log.debug("已生成消息{}"+message);
            list.notifyAll();

        }
    }
}

/**
 * Message类用final修饰和不添加属性的set方法,是防止其他类继承Message,或者修改属性
 * 这样Message就是线程安全的了
 */
@Slf4j(topic = "c.Message")
final class Message {
    private int id;
    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public Message(int id, String value) {
        this.id = id;
        this.value = value;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", value='" + value + '\'' +
                '}';
    }
}
10:54:14.468 [生产者1] DEBUG c.Message - 已生成消息{}Message{id=1, value='值1'}
10:54:14.469 [生产者0] DEBUG c.Message - 队列已满,生成者线程等待
10:54:15.472 [Thread-0] DEBUG c.Message - 已消费消息{}Message{id=2, value='值2'}
10:54:15.472 [生产者0] DEBUG c.Message - 已生成消息{}Message{id=0, value='值0'}
10:54:16.485 [Thread-0] DEBUG c.Message - 已消费消息{}Message{id=1, value='值1'}
10:54:17.495 [Thread-0] DEBUG c.Message - 已消费消息{}Message{id=0, value='值0'}
10:54:18.508 [Thread-0] DEBUG c.Message - 队列为空,消费者线程等待
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值