生产者与消费者
场景
生产者不断生成产品,并交给售货员,消费者不断从售货员那里消费产品
利用代码实现:
public class TestProductorAndConsumer {
public static void main(String[] args)
{
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(productor,"生产者A").start();
new Thread(consumer,"消费者B").start();
}
}
class Clerk{
private int product = 0;
//进货方法
public synchronized void get()
{
if(product>=10){
System.out.println("产品已满!无法添加");
}
else {
System.out.println(Thread.currentThread().getName()+":"
+ ++product);
}
}
//卖货方法
public synchronized void sale()
{
if(product<=0)
{
System.out.println("缺货!");
}
else {
System.out.println(Thread.currentThread().getName()+":"+
--product);
}
}
}
//生产者
class Productor implements Runnable{
private Clerk clerk;
public Productor(Clerk clerk){
this.clerk = clerk;
}
//不停地生产产品给店员
@Override
public void run() {
for(int i =0;i<20;i++)
{
clerk.get();
}
}
}
//消费者
class Consumer implements Runnable{
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
for(int i=0;i<20;i++){
clerk.sale();
}
}
}
代码逻辑:
- 生产者线程类:生产者线程开启后,不断调用售货员的
get
方法,将产品数加一并输出当前产品数 - 消费者线程类:消费者线程开启后,不断调用售货员的
sale
方法,将产品数减一并输出当前产品数 - 售货员类:拥有两个用
synchronized
修饰的方法
实际执行结果:
生产者A:1
生产者A:2
生产者A:3
生产者A:4
生产者A:5
生产者A:6
生产者A:7
生产者A:8
生产者A:9
生产者A:10
产品已满!无法添加
产品已满!无法添加
产品已满!无法添加
产品已满!无法添加
产品已满!无法添加
产品已满!无法添加
产品已满!无法添加
产品已满!无法添加
产品已满!无法添加
产品已满!无法添加
消费者B:9
消费者B:8
消费者B:7
消费者B:6
消费者B:5
消费者B:4
消费者B:3
消费者B:2
消费者B:1
消费者B:0
缺货!
缺货!
缺货!
缺货!
缺货!
缺货!
缺货!
缺货!
缺货!
缺货!
发生了什么?
生产者即使商品已满还是在生产,消费者即使商品没了还在消费
- 生产者线程:添加数据的线程,过快的话造成生产的数据丢失
- 消费者线程:删除或销毁数据的线程,过快的话造成重复数据或错误数据
怎么解决?
使用等待唤醒机制(注意大多数场景下生产者和消费者都有多个)
- 如果商品已满,生产者就不要继续生产进入等待;如果成功生产,说明商品没满,就唤醒其他线程可以继续生产
- 如果商品缺货,消费者就不要继续消费进入等待;如果成功消费,说明商品还有,就唤醒其他线程可以继续消费
具体解决代码
仅仅修改了clerk类
class Clerk{
private int product = 0;
//进货方法
public synchronized void get()
{
if(product>=10){
System.out.println("产品已满!无法添加");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
System.out.println(Thread.currentThread().getName()+":"
+ ++product);
this.notifyAll();
}
}
//卖货方法
public synchronized void sale()
{
if(product<=0)
{
System.out.println("缺货!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
System.out.println(Thread.currentThread().getName()+":"+
--product);
this.notifyAll();
}
}
}
- 因为调用
get
方法的一定是生产者,所以this.wait()
可以让生产者等待 - 同理在
sale
方法中一定是消费者,所以this.wait()
可以让消费者等待
实际执行结果:
生产者A:1
生产者A:2
生产者A:3
生产者A:4
生产者A:5
生产者A:6
生产者A:7
生产者A:8
生产者A:9
生产者A:10
产品已满!无法添加
消费者B:9
消费者B:8
消费者B:7
消费者B:6
消费者B:5
消费者B:4
消费者B:3
消费者B:2
消费者B:1
消费者B:0
缺货!
生产者A:1
生产者A:2
生产者A:3
生产者A:4
生产者A:5
生产者A:6
生产者A:7
生产者A:8
生产者A:9
消费者B:8
消费者B:7
消费者B:6
消费者B:5
消费者B:4
消费者B:3
消费者B:2
消费者B:1
消费者B:0
可以看到不会出现多次重复生产或消费的情况了
仍然存在的问题:
有可能造成某个线程一直处在wait
状态(去掉else
可能可以解决)
最重要的,如果是多个生产者,消费者,可能产生虚假唤醒问题
虚假唤醒:
- 假设两个消费者(两个以上同理)线程A和B调用
sale
方法,线程A发现商品数目为0,进入wait
状态,线程B发现商品数也为0,也进入wait
状态 - 此时A和B都进入了
wait
状态,生产者进入,添加了新的商品,进而调用notifyAll
唤醒所有线程 - 消费者A被唤醒,从
wait
状态继续执行(即从调用wait
方法代码行之后执行), 消费成功将商品数减一,然后消费者B也进行了同样的一轮操作,这个时候商品只生产了一个,商品数实际上被消费了两次
怎么解决?
官方文档中提供了解决方法,即
wait
方法应该总是存在循环中,用while循环代替if判断,最终改正过的代码为:
还是仅修改Clerk类
class Clerk{
private int product = 0;
//进货方法
public synchronized void get()
{
while (product>=10){
System.out.println("产品已满!无法添加");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+":"
+ ++product);
this.notifyAll();
}
//卖货方法
public synchronized void sale()
{
while (product<=0)
{
System.out.println("缺货!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+":"+
--product);
this.notifyAll();
}
}
- 用while循环代替了if判断
- 去掉了else代码段