[Java]Java实现生产者消费者问题

原文链接:https://blog.csdn.net/ldx19980108/article/details/81707751 (原文代码有误作出更正)

一、问题描述

生产者消费者问题(Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例。生产者生成一定量的数据放到缓冲区中,然后重复此过程;与此同时,消费者也在缓冲区消耗这些数据。生产者和消费者之间必须保持同步,要保证生产者不会在缓冲区满时放入数据,消费者也不会在缓冲区空时消耗数据。不够完善的解决方法容易出现死锁的情况,此时进程都在等待唤醒。
示意图:
生产者消费者

二、解决方法

思路

采用某种机制保护生产者和消费者之间的同步。有较高的效率,并且易于实现,代码的可控制性较好,属于常用的模式。
在生产者和消费者之间建立一个管道。管道缓冲区不易控制,被传输数据对象不易于封装等,实用性不强。

解决问题的核心

保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用信号或加锁机制,保证资源在任意时刻至多被一个线程访问。

Java能实现的几种方法

  1. wait() / notify()方法
  2. await() / signal()方法
  3. BlockingQueue阻塞队列方法
  4. 信号量
  5. 管道

三、代码实现

1.wait() / notify()方法

当缓冲区已满时,生产者线程停止执行,放弃锁,使自己处于等状态,让其他线程执行;
当缓冲区已空时,消费者线程停止执行,放弃锁,使自己处于等状态,让其他线程执行。
当生产者向缓冲区放入一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态;
当消费者从缓冲区取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。
仓库Storage.java

import java.util.LinkedList;

/* 仓库 */
public class Storage {


    /* 仓库容量 */
    private final int MAX_SIZE = 10;
    /* 仓库载体 */
    private LinkedList<Object> list = new LinkedList<>();

    public void produce(){
        synchronized (list){
            if(list.size() == MAX_SIZE){
                System.out.println("[生产者" + Thread.currentThread().getName() + "] 仓库已满");
                try{
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            list.add(new Object());
            System.out.println("[生产者" + Thread.currentThread().getName() + "生产1个产品] 现库存:" + list.size());
            list.notifyAll();
        }
    }

    public void consume(){
        synchronized (list){
            if(list.size() == 0){
                System.out.println("[消费者" + Thread.currentThread().getName() + "] 仓库为空");
                try{
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            list.remove();
            System.out.println("[消费者" + Thread.currentThread().getName() + "消费1个产品] 现库存:" + list.size());
            list.notifyAll();
        }
    }
}

生产者:

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

    private String name;
    private Storage storage;

    public Producer(Storage storage, String name){
        this.storage = storage;
        this.name = name;
    }
    @Override
    public void run() {
        while (true){
            try{
                Thread.sleep((long) (5000*Math.random()));
                storage.produce();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者:

public class Consumer implements Runnable {

    private Storage storage;
    private String name;
    private Consumer(){}

    public Consumer(Storage storage,String name){
        this.storage = storage;
        this.name = name;
    }
    @Override
    public void run() {
        while (true){
            synchronized (storage){
                try {
                    Thread.sleep((long) (1000*Math.random()));
                    storage.consume();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果:

p1-------------Thread-0
p2-------------Thread-1
p3-------------Thread-2
c1-------------Thread-3
c2-------------Thread-4
c3-------------Thread-5
[消费者Thread-3] 仓库为空
[生产者Thread-0生产1个产品] 现库存:1
[消费者Thread-3消费1个产品] 现库存:0
[生产者Thread-2生产1个产品] 现库存:1
[消费者Thread-3消费1个产品] 现库存:0
[消费者Thread-3] 仓库为空
[生产者Thread-2生产1个产品] 现库存:1
[消费者Thread-3消费1个产品] 现库存:0
[生产者Thread-0生产1个产品] 现库存:1
[消费者Thread-3消费1个产品] 现库存:0
[消费者Thread-3] 仓库为空
[生产者Thread-1生产1个产品] 现库存:1
[消费者Thread-3消费1个产品] 现库存:0
[消费者Thread-3] 仓库为空
[生产者Thread-0生产1个产品] 现库存:1
[消费者Thread-3消费1个产品] 现库存:0
[生产者Thread-1生产1个产品] 现库存:1
[生产者Thread-2生产1个产品] 现库存:2
[生产者Thread-1生产1个产品] 现库存:3
[消费者Thread-3消费1个产品] 现库存:2
[消费者Thread-3消费1个产品] 现库存:1
[消费者Thread-3消费1个产品] 现库存:0

一个生产者线程运行produce方法,睡眠时间随机;一个消费者运行一次consume方法,睡眠随机,为了验证是否出现异常默认生产者平均速度小于消费者平均速度。此次实验过程中,有3个生产者和3个消费者,也就是我们说的多对多的情况。仓库的容量为10。

注意:

notifyAll()方法可使所有正在等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的哪个线程最先执行,但也有可能是随机执行的,这要取决于JVM虚拟机的实现。即最终也只有一个线程能被运行,上述线程优先级都相同,每次运行的线程都不确定是哪个,后来给线程设置优先级后也跟预期不一样,还是要看JVM的具体实现吧。

发布了84 篇原创文章 · 获赞 23 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_36254699/article/details/100178627