生产者与消费者设计模式(一对一,一对多,多对多)——Java实现

  • Post author:
  • Post category:java


第一次发文,如有错误,欢迎指正。

1.首先,我们创建一个消费对象。

public class StaticVariate {
    public static List Pc = new ArrayList();
}

2.创建生产者

public class Prodecer {
    public String lock;

    public Prodecer(String lock) {
        this.lock = lock;
    }

    public void Prodec() throws InterruptedException {
        synchronized (lock){
            if(StaticVariate.Pc.size() != 0){
                System.out.println(Thread.currentThread().getName()+" Begin Waiting*");
                //当Pc不需要生产时,将调用此方法的线程放入预执行队列,等待唤醒
                lock.wait();
                System.out.println(Thread.currentThread().getName()+" End Waiting*");
            }
            StaticVariate.Pc.add(1);
            System.out.println("生产后 PC="+StaticVariate.Pc);
            //生产完成后唤醒某个等待lock对象锁的其他线程,即消费者线程
            //注意:notify后当前线程并不会马上释放lock对象锁,其他线程也并不会立刻获得该对象锁,得等当前线程执行完毕
            lock.notify();
            System.out.println(Thread.currentThread().getName()+" 生产结束*");
        }
    }
}

3.创建消费者

public class Consumer {
    public String lock;

    public Consumer(String lock) {
        this.lock = lock;
    }

    public void consum() throws InterruptedException {
        synchronized (lock){
            if(StaticVariate.Pc.size() == 0){
                System.out.println(Thread.currentThread().getName()+" Begin Waiting*");
                //当Pc被消费后,将调用此方法的线程放入预执行队列,等待唤醒
                lock.wait();
                System.out.println(Thread.currentThread().getName()+" End Waiting*");
            }
            StaticVariate.Pc.remove(0);
            System.out.println("消费后 PC="+StaticVariate.Pc);
            //消费完成后唤醒某个等待lock对象锁的其他线程,即生产者线程
            //注意:notify后当前线程并不会马上释放lock对象锁,其他线程也并不会立刻获得该对象锁,得等当前线程执行完毕
            lock.notify();
            System.out.println(Thread.currentThread().getName()+" 消费结束*");
        }
    }
}

4.创建生产者线程

public class ProdecerThread extends Thread{
        private Prodecer p;

        public ProdecerThread(Prodecer p) {
            this.p = p;
        }

        @Override
        public void run(){
            try {
                while(true){
                    //持续生产
                    p.Prodec();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

5.创建消费者线程

public class ConsumerThread extends Thread{
        private Consumer c;

        public ConsumerThread(Consumer p) {
            this.c = p;
        }

        @Override
        public void run(){
            try {
                while(true){
                    //持续消费
                    c.consum();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

6..一生产者对一消费者测试

    public static void main(String[] args) throws InterruptedException {
        String lock = "";
        Prodecer p = new Prodecer(lock);
        Consumer c = new Consumer(lock);
        ProdecerThread prodecerThread1 = new ProdecerThread(p);
        prodecerThread1.setName("生产者1");
        ConsumerThread consumerThread1  = new ConsumerThread(c);
        consumerThread1.setName("消费者1");

        consumerThread1.start();
        prodecerThread1.start();
    }

7.一生产者对一消费者测试结果

一对一测试结果

显然,在一对一的情况下上述代码是没问题的,PC始终为【1】或【】。

但是我们如果加入第二个消费者那么问题就来了,程序是否还能正确运行呢?下面开始测试。

7..一生产者对多(2)消费者测试1

    public static void main(String[] args) throws InterruptedException {
        String lock = "";
        Prodecer p = new Prodecer(lock);
        Consumer c = new Consumer(lock);
        ProdecerThread prodecerThread1 = new ProdecerThread(p);
        prodecerThread1.setName("生产者1");
        ConsumerThread consumerThread1  = new ConsumerThread(c);
        consumerThread1.setName("消费者1");
        ConsumerThread consumerThread2  = new ConsumerThread(c);
        consumerThread2.setName("消费者2");

        consumerThread1.start();
        consumerThread2.start();
        prodecerThread1.start();
    }

8.一生产者对多(2)消费者测试结果1

一对多错误

为什么会出现这样的结果呢?我们来一行一行分析:

(1)两个消费者分别进入预执行队列

(2)生产者1进行生产之后PC=【1】,释放对象锁

(3)消费者1拿到对象锁,在消费之后PC=【】,进入预执行队列之后,释放对象锁

(4)线程规划器唤醒消费者2,在消费者2拿到对象锁之后,开始消费但是此时PC=【】,所以消费者2在进行消费时会报错,因为此时PC中没有元素。

经过分析之后我们找到了问题①所在,那么我们就定位到代码:

if(StaticVariate.Pc.size() == 0){
    System.out.println(Thread.currentThread().getName()+" Begin Waiting*");
    //当Pc被消费后,将调用此方法的线程放入预执行队列,等待唤醒
    lock.wait();
    System.out.println(Thread.currentThread().getName()+" End Waiting*");
}

因为我们使用的是if语句,当等待wait的条件发生变化时,并不会进行第二次判断,所以我们需要使用while语句,在线程被唤醒之后再次进行判断。

9.修改过后的消费者

public class Consumer {
    public String lock;

    public Consumer(String lock) {
        this.lock = lock;
    }

    public void consum() throws InterruptedException {
        synchronized (lock){
            while(StaticVariate.Pc.size() == 0){
                System.out.println(Thread.currentThread().getName()+" Begin Waiting*");
                //当Pc被消费后,将调用此方法的线程放入预执行队列,等待唤醒
                lock.wait();
                System.out.println(Thread.currentThread().getName()+" End Waiting*");
            }
            StaticVariate.Pc.remove(0);
            System.out.println("消费后 PC="+StaticVariate.Pc);
            //消费完成后唤醒某个等待lock对象锁的其他线程,即生产者线程
            //注意:notify后当前线程并不会马上释放lock对象锁,其他线程也并不会立刻获得该对象锁,得等当前线程执行完毕
            lock.notify();
            System.out.println(Thread.currentThread().getName()+" 消费结束*");
        }
    }
}

10.修改过后的生产者

public class Prodecer {
    public String lock;

    public Prodecer(String lock) {
        this.lock = lock;
    }

    public void Prodec() throws InterruptedException {
        synchronized (lock){
            while(StaticVariate.Pc.size() != 0){
                System.out.println(Thread.currentThread().getName()+" Begin Waiting*");
                //当Pc不需要生产时,将调用此方法的线程放入预执行队列,等待唤醒
                lock.wait();
                System.out.println(Thread.currentThread().getName()+" End Waiting*");
            }
            StaticVariate.Pc.add(1);
            System.out.println("生产后 PC="+StaticVariate.Pc);
            //生产完成后唤醒某个等待lock对象锁的其他线程,即消费者线程
            //注意:notify后当前线程并不会马上释放lock对象锁,其他线程也并不会立刻获得该对象锁,得等当前线程执行完毕
            lock.notify();
            System.out.println(Thread.currentThread().getName()+" 生产结束*");
        }
    }
}

11.一生产者对多(2)消费者测试2

    public static void main(String[] args) throws InterruptedException {
        String lock = "";
        Prodecer p = new Prodecer(lock);
        Consumer c = new Consumer(lock);
        ProdecerThread prodecerThread1 = new ProdecerThread(p);
        prodecerThread1.setName("生产者1");
        ConsumerThread consumerThread1  = new ConsumerThread(c);
        consumerThread1.setName("消费者1");
        ConsumerThread consumerThread2  = new ConsumerThread(c);
        consumerThread2.setName("消费者2");

        consumerThread1.start();
        consumerThread2.start();
        prodecerThread1.start();
    }

12.一生产者对多(2)消费者测试结果2

一对多错误2

咦,为什么不运行呢? 我们继续分析日志,当消费者2拿到对象锁进行再次判定的时候检测到PC为空,进入预执行队列,释放对象锁,此时生产者1与消费者1,2都在预执行队列当中,没有线程唤醒生产者1,这样就形成了我们常说的‘假死’。

找到问题所在就好解决了,我们知道notify是通知在等待当前线程所持有的对象的对象锁的线程,如果有多个等待线程,那么就由线程规划器随机唤醒出一个预执行队列中的线程对其发出通知。如果需要唤醒所有线程则应该使用notifyAll。

因此我们再次优化代码将生产者,消费者中notify替换成notifyAll再次进行测试。

lock.notifyAll();

13.一生产者对多(2)消费者测试结果3

一对多成功

结果是喜人的,代码能够正确并稳定的执行。

既然一对多没问题了,那么我们来看看多对多会不会出现问题呢,我们再加入一个生产者试试。

14.多(2)生产者对多(2)消费者测试

public static void main(String[] args) throws InterruptedException {
        String lock = "";
        Prodecer p = new Prodecer(lock);
        Consumer c = new Consumer(lock);
        ProdecerThread prodecerThread1 = new ProdecerThread(p);
        prodecerThread1.setName("生产者1");
        ConsumerThread consumerThread1  = new ConsumerThread(c);
        consumerThread1.setName("消费者1");
        ProdecerThread prodecerThread2 = new ProdecerThread(p);
        prodecerThread2.setName("生产者2");
        ConsumerThread consumerThread2  = new ConsumerThread(c);
        consumerThread2.setName("消费者2");

        consumerThread1.start();
        consumerThread2.start();
        prodecerThread1.start();
        prodecerThread2.start();
    }

15.多(2)生产者对多(2)消费者测试结果

多对多正确

完美,在多对多的情况下我们代码依然能够按照我们预期的执行,PC始终在【1】和【】之间被生产与消费,各线程之间也没有出现问题。

原创不易,转载请指明出处,如有帮助到您,不胜欢喜。



版权声明:本文为qq_37592936原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。