多生产者与消费者仓库剩余问题

来源:7-6 用wait/notify实现

weixin_慕UI5143857

2021-04-12

悟空老师. 您好:

在程序有多个消费者与生产者情况下, storage会出现无法被完全消费完的情况, 代码如下:

public class ProducerConsumerModelWaitNotify {

    public static void main(String[] args) throws InterruptedException {
        EventStorage storage = new EventStorage(10);
        Producer producer = new Producer(storage);
        Consumer consumer = new Consumer(storage);
        new Thread(producer).start();
        new Thread(producer).start();
        new Thread(consumer).start();
        new Thread(consumer).start();
        Thread.sleep(1000);
        System.out.println(storage.storage.size());
    }

}

/**
 * 生产者
 */
class Producer implements Runnable {

    private final EventStorage storage;

    public Producer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for(int i=0; i<3; i++) {
            storage.put(new Date());
        }
    }
}

/**
 * 消费者
 */
class Consumer implements Runnable {

    private final EventStorage storage;

    public Consumer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for(int i=0; i<3; i++) {
            storage.take();
        }
    }
}

/**
 * Storage队列
 */
class EventStorage {

    public int maxSize;
    public LinkedList<Date> storage;

    public EventStorage(int maxSize) {
        this.maxSize = maxSize;
        this.storage = new LinkedList<>();
    }

    public synchronized void put(Date date) {
        // 判断仓库是否满了, 满了进入watting
        if(this.storage.size() == maxSize) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;
            }
        }
        this.storage.add(date);
        System.out.println("仓库里有了: " + this.storage.size() + "个产品");
        // 唤醒与这个对象锁有关的其中一个生产者
        notify();
    }

    public synchronized void take() {
        // 仓库是否是空了. 空了进入watting
        if(this.storage.size() == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;
            }
        }
        // 取出产品, 并唤与此锁有关的处于watting状态的生产者生产
        System.out.println("消费者拿到了" + storage.poll() + "仓库还剩下" + storage.size());
        notify();
    }
}

两个生产者, 两个消费者, 每个生产者生产3次, 每个消费者消费3次.
程序都能正常结束, 正常停止.
但是:
在多次运行之后, 发现了如下运行结果:

仓库里有了: 1个产品
仓库里有了: 2个产品
仓库里有了: 3个产品
消费者拿到了Mon Apr 12 23:26:37 CST 2021仓库还剩下2
消费者拿到了Mon Apr 12 23:26:37 CST 2021仓库还剩下1
消费者拿到了Mon Apr 12 23:26:37 CST 2021仓库还剩下0
消费者拿到了null仓库还剩下0
仓库里有了: 1个产品
仓库里有了: 2个产品
仓库里有了: 3个产品
消费者拿到了Mon Apr 12 23:26:37 CST 2021仓库还剩下2
消费者拿到了Mon Apr 12 23:26:37 CST 2021仓库还剩下1
1

有一个消费者在仓库空的情况下取到了一个空值, 导致程序执行完毕, stroage还存在一个产品

按道理, storage种take() 与 put() 都是被synchronized修饰的, 并且持有的moniter都是storage这个对象锁.所以无论多少生产者多少消费者在操作storage应该仅一个线程能够去访问storage, 怎么会有线程能够取到null?

最后经过尝试, 如果在消费者与生产者再加一把锁(代码如下), 可以解决仓库剩余问题, 但是个人无法解释原因:

/**
 * 生产者
 */
class Producer implements Runnable {

    private final EventStorage storage;

    public Producer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        synchronized (this) {
            for(int i=0; i<3; i++) {
                storage.put(new Date());
            }
        }
    }
}

/**
 * 消费者
 */
class Consumer implements Runnable {

    private final EventStorage storage;

    public Consumer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        synchronized (this) {
            for(int i=0; i<3; i++) {
                storage.take();
            }
        }
    }
}

恳请老师指点一二.

写回答

1回答

weixin_慕UI5143857

提问者

2021-04-13

找到问题了,notify唤醒的可能是另一个消费者

0
0

线程八大核心+Java并发原理及企业级并发解决方案

完整的并发知识网络+丰富的工作内容分享+50余道并发高频面试题

2512 学习 · 939 问题

查看课程