生产者-消费者模式(使用wait & notifyAll实现)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/liuzhixiong_521/article/details/86666240

概述

生产者与消费者共用一个队列。

  • 当队列满时,生产者无法再继续生产,所以生产者阻塞;
  • 当队列空时,消费者无法再继续消费,所以消费者阻塞。

关键操作模拟

  • 队列:本文以LinkedList作为队列,有兴趣的朋友也可以使用其他队列(比如循环队列,阻塞队列等)。
  • 阻塞:我们以Object类的wait()方法使线程阻塞(此处蕴含细节:为什么wait()和notifyAll()方法属于Object类,而不属于线程?参考wait()、notify()和notifyAll()方法为什么属于Object)。
  • 唤醒:我们以Object类的notifyAll()方法唤醒被阻塞的线程(此处蕴含细节,notify()与notifyAll()方法有什么不同?)。

代码实现

队列实现

public class Storage {
    private static final int MAX_SIZE = 10;
    private static LinkedList<String> queue = new LinkedList<String>();
    
    public void produce(String s) {
        synchronized (queue) {
            while (queue.size() == MAX_SIZE) {
                System.out.println("仓库已满");

                try {
                    queue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("生产了 " + s);
            queue.add(s);
            queue.notifyAll();
        }
    }
    public String consumer() {
        synchronized (queue) {
            while (queue.size() == 0) {
                System.out.println("仓库已空");
                try {
                    queue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            String s = queue.pollFirst();
            queue.notifyAll();
            System.out.println("消费了 " + s);
            return s;
        }
    }
}

生产者实现

public class Producer extends Thread {
    private Storage storage;

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

    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.produce(String.valueOf(i));
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者实现

class Consumer extends Thread {
    private Storage storage;

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

    public void run() {
        while(true) {
            storage.consumer();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

测试实现

public class Test {
    public static void main(String[] args) {
        Storage storage = new Storage();
        Producer producer = new Producer(storage);
        Consumer consumer = new Consumer(storage);
        producer.start();
        consumer.start();
    }
}

打印结果

生产了 0
消费了 0
生产了 1
生产了 2
生产了 3
生产了 4
生产了 5
生产了 6
生产了 7
生产了 8
生产了 9
生产了 10
仓库已满
消费了 1
生产了 11
仓库已满
消费了 2
生产了 12
仓库已满
消费了 3
生产了 13
仓库已满
消费了 4
生产了 14
仓库已满
消费了 5
生产了 15
仓库已满
消费了 6
.
.
.

代码解惑

解惑

Storage的produce和consume方法中,为什么判断队列满和判断队列空时使用while,而不是使用if呢?以producer方法为例,代码为

synchronized (queue) {
    while (queue.size() == MAX_SIZE) {
        System.out.println("仓库已满");

        try {
            queue.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println("生产了 " + s);
    queue.add(s);
    queue.notifyAll();
}

简化为

synchronized (queue) {
    while (queue.size() == MAX_SIZE) {
        queue.wait();
    }
    System.out.println("生产了 " + s);
    queue.add(s);
    queue.notifyAll();
}

有的同学认为此处可以用if,但是经过仔细推敲,其实用if还是有问题的。此处我们模拟两个线程t1和t2,并认为此时队列已满,按照以下流程执行

  • t1获取锁并进入同步代码,并执行while方法体,由于队列已满,则t1调用wait进入阻塞状态,并放弃锁。
  • 此时t2恰好获取到了锁,也执行while方法体,由于此时队列已满,t2调用wait也进入阻塞状态,并放弃锁。
  • 假设此时有一个消费者线程从队列消费了一个产品,并调用了notifyAll()方法,唤醒了t1和t2,假设t1又抢占到了锁,继续执行while,此时不满足
    queue.size() == MAX_SIZE,则开始生产,最后执行notifyAll()方法,此过程并无问题。
  • 接下来,假如t2恰好获取到了锁,并开始往下执行,因为此时队列已满,并不能继续生产,如果使用while语句,则会继续判断queue.size() == MAX_SIZE条件,使t2继续阻塞,但是如果使用if语句,则不会继续判断,而是直接去生产,而此时队列已满,肯定会出现异常。

总结

我们以生产的过程,解释了为什么此处必须使用while而不是使用if,消费的过程与生产过程类似,此处不再赘述。有兴趣的同学也可以参考一下EffectJava第三版,里面有关于while问题的详细叙述。

温馨提示

  • 如果您对本文有疑问,请在评论部分留言,我会在最短时间回复。
  • 如果本文帮助了您,也请评论,作为对我的一份鼓励。
  • 如果您感觉我写的有问题,也请批评指正,我会尽量修改。

猜你喜欢

转载自blog.csdn.net/liuzhixiong_521/article/details/86666240