一、介绍
:
(一)、概念:
如果要实现以下操作流程,使用Java代码来实现:
- 多个蛋糕师生产蛋糕,多个消费者购买蛋糕;
- 蛋糕的最大库存为5个;
- 早生产的蛋糕先被销售,最后被生产的蛋糕要最后被售出
如果要实现这个过程,一定要借助Java线程的并发协作来做。其实这在Java中叫做
生产者消费者模型
(确切说应该是“生产者-消费者-仓储”模型)。
对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的。就像学习每一门编程语言一样,Hello World!都是最经典的例子。
(二)、 生产者消费者模型,应该明确一下几点:
1、生产者仅仅在仓储未满时候生产,
仓满
则停止生产。
2、消费者仅仅在仓储有产品时候才能消费,
仓空
则等待。
3、当消费者发现仓储没产品可消费时候会
通知生产者生产
。
4、生产者在生产出可消费产品时候,应该
通知等待的消费者去消费
。
(三)、核心技术:
Java中提供了三个非常重要方法用来解决“
生产者-消费者-仓储
”模型 。
- wait():让当前线程进入阻塞状态,将这个线程存储到等待池中,并释放当前线程的所获得的锁
- notify():唤醒等待池中的一个线程(随机)
- notifyAll():唤醒等待池中所有的线程
【注意】
- wait、notify、notifyAll三个方法必须被在同步代码中被调用
- 这些方法都是用于操作线程状态,那么就必须要明确到底操作的是哪个锁上的线程
二、代码实现
(一)、实现步骤:
1、定义仓库类:
- 实现生产和消费方法
- 在仓满和仓空时等待(wait)和唤醒(notify)
- 使用同步锁
2、定义生产者线程类
- 定义构造方法,初始化仓库类
- 在run方法中无限循环执行仓库类的生产方法
3、定义消费者线程类
- 定义构造方法,初始化仓库类
- 在run方法中无限循环执行仓库类的消费方法
(二)、核心代码
public class ProducerConsumerGodown {
public static void main(String[] args) throws Exception {
new ProducerConsumerGodown().startTest();
}
public void startTest() {
Godown godown = new Godown();
Producer producer1 = new Producer(godown);
producer1.setName("1号糕点师:");
producer1.start();
Producer producer2 = new Producer(godown);
producer2.setName("2号糕点师:");
producer2.start();
Producer producer3 = new Producer(godown);
producer3.setName("3号糕点师:");
producer3.start();
Producer producer4 = new Producer(godown);
producer4.setName("4号糕点师:");
producer4.start();
Producer producer5 = new Producer(godown);
producer5.setName("5号糕点师:");
producer5.start();
Consumer consumer1 = new Consumer(godown);
consumer1.setName("张三:");
consumer1.start();
Consumer consumer2 = new Consumer(godown);
consumer2.setName("李四:");
consumer2.start();
Consumer consumer3 = new Consumer(godown);
consumer3.setName("王五:");
consumer3.start();
}
class Godown {
// 定义柜台中的蛋糕
private LinkedList<Object> storeHouse = new LinkedList<Object>();
// 定义柜台中最大允许的蛋糕库存量
private int MAX = 5;
private Object lock = new Object();
// 生产蛋糕
public void produce() {
try {
synchronized (lock) {
// if(storeHouse.size() >=
// MAX)//注意这里只能用while循环不能用if来判断,if判断只适合1对1的关系,就是只有2个线程,
// 如果多个线程用if判断的话,就会出现逻辑错误。
// 所以在多线程中都用while来做判断,不用if
while (storeHouse.size() == MAX) {
System.out.println("蛋糕库存已满,糕点师停止生产,等待售出!---库存量:"
+ storeHouse.size());
lock.wait();
}
Object newObj = new Object();
// 往柜台中添加蛋糕
if (storeHouse.add(newObj)) {
System.out.println(Thread.currentThread().getName()
+ "生产入库!---产品编号:" + newObj.hashCode()
+ "---库存 增加 至:" + storeHouse.size());
// 唤醒在此对象锁上处于等待状态的所有线程
lock.notifyAll();
}
}
Thread.sleep((long) (Math.random() * 3000));
} catch (InterruptedException e) {
System.out.println(e.toString());
}
}
public void consume() {
try {
synchronized (lock) {
// 在多线程中都用while来做判断,不用if
while (storeHouse.size() == 0) {
System.out.println("蛋糕已售空,请消费者等待!---库存量:"
+ storeHouse.size());
lock.wait();
}
// 将集合中的第一个元素移除,也就是将最早生产的蛋糕售出
// LinkedList与ArrayList相比,后者不具有removeFirst方法
Object object = storeHouse.removeFirst();
System.out.println(Thread.currentThread().getName()
+ "购买产品!---产品编号:" + object.hashCode()
+ "---库存 减少 至:" + storeHouse.size());
// 唤醒在此对象锁上处于等待状态的所有线程
lock.notifyAll();
}
Thread.sleep((long) (Math.random() * 3000));
} catch (InterruptedException e) {
System.out.println(e.toString());
}
}
}
class Producer extends Thread {
private Godown godown;
public Producer(Godown godown) {
this.godown = godown;
}
public void run() {
while (true) {
// 生产
godown.produce();
}
}
}
// 定义消费者类
class Consumer extends Thread {
private Godown godown;
public Consumer(Godown godown) {
this.godown = godown;
}
public void run() {
while (true) {
// 消费
godown.consume();
}
}
}
}
【附加:】
1、
ArrayList
查找速度
快
,原因是ArrayList的底层用得是数组结构;插入和删除
慢
,因为需要移动后续的对象;
2、
LinkedList
查找速度
慢
,原因是迭代循环;插入和删除
快
,原因是只更新引用。