Java并发编程(五):生产者与消费者之虚假唤醒

生产者与消费者

场景

生产者不断生成产品,并交给售货员,消费者不断从售货员那里消费产品

利用代码实现:

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代码段
原创文章 40 获赞 16 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43925277/article/details/105160779
今日推荐