生产者消费者模式-设计模式-并发编程(Java)

1、保护性暂停解耦

图中Futures就好比居民楼的信箱(每个信箱都有房间编号),左侧t0,t2,t4就好比等待邮件的居民,右侧的t1,t3,t5就好比邮递员。

如果需要在多个类之间使用GuardedObject对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类,这样不仅能够解耦结果等待者和结果生产者,还能够同时支持多个任务的管理。

场景:邮递员送信,居民收信,信箱里面有很多格子,每个格子对应居民房间,编号。邮递员根据信件的房间编号,把信件投入相应的信箱小格子;居民根据自己的房间编号去对应的小格子中去信件,读取内容。

/**
 * 信箱类
 */
public class MailBoxes {
    // 容器,存放信件
    private static Hashtable<String, GuardedDecouple> container = new Hashtable<>();

    /**
     * 根据居民房间编号初始化信箱
     * @param identifiers   居民房间编号
     */
    public static void init(String[] identifiers) {
        for (String identifier: identifiers) {
            container.put(identifier, new GuardedDecouple(identifier));
        }
    }

    /**
     * 根据房间编号获取信件
     * @param identifier    房间编号
     * @return              信件
     */
    public synchronized  static GuardedDecouple getLetter(String identifier) {
        return container.get(identifier);
    }

    /**
     * 设置信件-快递员把信件投入收件箱
     * @param guardedDecouple
     */
    public synchronized static void setLetter(GuardedDecouple guardedDecouple) {
        container.put(guardedDecouple.getIdentifier(), guardedDecouple);
    }
}

/**
 * 居民类
 */
@Slf4j(topic = "d.Resident")
public class Resident implements Runnable{
    private String identifier;

    public Resident(String identifier) {
        this.identifier = identifier;
    }

    @Override
    public void run() {
        // 收信
        GuardedDecouple letter = MailBoxes.getLetter(identifier);
        log.debug("等待收信");
        Object content = letter.get(5000);
        log.debug("居民房间编号:{},信已收到,内容:{}", letter.getIdentifier(), content);
    }
}

/**
 * 邮递员
 */
@Slf4j(topic = "d.Postman")
public class Postman implements Runnable{
    private String identifier;
    private String mail;

    public Postman(String identifier, String mail) {
        this.identifier = identifier;
        this.mail = mail;
    }

    @Override
    public void run() {
        // 送信件
        log.debug("信已送到,居民编号:{}", identifier);
        GuardedDecouple letter = MailBoxes.getLetter(identifier);
        letter.setResponse(mail);
        MailBoxes.setLetter(letter);

    }
}


/**
 * 送信测试
 */
@Slf4j(topic = "d.GuardedTest03")
public class GuardedTest03 {
    public static void main(String[] args) throws InterruptedException {

        // 信件
        HashMap<String, String> mails = new HashMap<>();
        mails.put("101", "你好吗?老朋友");
        mails.put("102", "要买房吗?");
        mails.put("307", "您的快递到了,请签收");
        // 居民房间编号
        String[] identifiers = {"101", "102", "307"};
        // 初始化信箱
        MailBoxes.init(identifiers);
        // 模拟3个居民
        for (String identifier: identifiers) {
            new Thread(new Resident(identifier), "居民" + identifier).start();
        }
        // 模拟3个快递员
        TimeUnit.SECONDS.sleep(1);
        for (String identifier: identifiers) {
            new Thread(new Postman(identifier, mails.get(identifier)), "邮递员" + identifier).start();
        }
    }
}
  • 这里只模拟了信箱里面小格子只接收1封信件,如果想要接收多个信件,自行改造为集合类型。

2、生产者消费者模式

邮递员和居民一一对应,成本太高。以送‘四通一达’送快递为例,通常一片区域,每家快递公司配置一名快递员,快递到达快递公司,指派该片快递员送至小区统一快递点。居民也不需要提前等待取件,什么时候有空了什么时候去取件。这里可以该为生产者消费者模式实现。

  • 与前面的保护性暂停不同,不需要产生结果和消费结果的线程一一对应。

  • 消费队列可以平衡生产和消费的线程资源

  • 生产者仅负责产生结果,不关心数据什么时间、如何被处理;消费者负责消息的处理。

  • 消息队列有容量限制,当消息队列满时,不能在往消息队列添加消息;当消息队列空时,不能取消息。

  • JDK中的各种阻塞队列,采用的就是这种模式

生产者消费者小案例:生产者若干,消费者若干。生产者生产消息放入消息队列,消费者从消息队列中取消息;等消息队列空时,消费者等待生产者放入消息;当消息队列满时,生产者等待消费者取消息;本例模拟3个生产者,1个消费者。

/**
 * 消息类
 */
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Message {
    /** 唯一标志 */
    private int id;
    /** 内容 */
    private Object value;
}

/**
 * 消息队列
 */
@Slf4j(topic = "c.MessageQueue")
public class MessageQueue {
    /** 消息容器 */
    LinkedList<Message> queue = new LinkedList<>();
    /** 队列容量 */
    private int capacity;

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

    /**
     * 获取消息
     * @return  消息
     */
    public Message getMessage() {
        synchronized (queue) {
            // 检查消息队列是否为空
            while (queue.size() == 0) {
                // 消息队列为空不能获取消息,生产者线程等待
                try {
                    log.debug("消息队列为空,线程-{}等待",Thread.currentThread().getName());
                    queue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            Message message = queue.removeFirst();
            log.debug("线程-{}消费了消息:{}", Thread.currentThread().getName(), message);
            // 通知生产者可以添加消息
            queue.notifyAll();
            return message;
        }
    }

    /**
     * 添加消息
     * @param message
     */
    public void putMessage(Message message) {
        synchronized (queue) {
            // 检查消息队列是否已满
            while (queue.size() == capacity) {
                // 如果消息队列已满,生产者线程等待
                try {
                    log.debug("消息队列已满,线程-{}等待",Thread.currentThread().getName());
                    queue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            queue.addLast(message);
            log.debug("线程-{}生产了消息:{}", Thread.currentThread().getName(), message);
            // 通知消费者可以获取消息
            queue.notifyAll();
        }
    }
}

@Slf4j(topic = "c.MessageQueueTest01")
public class MessageQueueTest01 {
    public static void main(String[] args) throws InterruptedException {
        // 创建消息队列,初始容量2
        MessageQueue queue = new MessageQueue(2);
        // 模拟3个生产者线程
        for (int i = 0; i < 3; i++) {

            int id = i;
            new Thread(() -> {
                queue.putMessage(new Message(id, "啦啦啦啦" + id));
            }, "生产者" + i).start();
        }

        // 等待1s
        TimeUnit.SECONDS.sleep(1);

        // 模拟1个消费者线程
        new Thread(() -> {
            while (true) {
                Message message = queue.getMessage();
            }
        }, "消费者").start();

    }
}

仓库地址:https://gitee.com/gaogzhen/concurrent

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gaog2zh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值