逐步详细分析Java生产者-消费者问题,为什么使用 while 而不用 if ?

内容
1、存钱取钱(逐步分析生产者消费者问题)
2、使用 wait-notifyAll 实现生产者-消费者
3、使用 LinkedBlockingQueue 实现生产者-消费者

存钱-取钱

1、一个人存钱,一个人取钱:在卡中没钱的时候存钱,有钱的时候取钱,交替执行

  • flag为true时:只能取钱,存钱会被阻塞

  • flag为false时:只能存钱,取钱会被阻塞

public class BankCard {
    private double money;

    // true:只能取钱
    private boolean flag;

    public synchronized void save(double addMoney) {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money += addMoney;
        System.out.println(Thread.currentThread().getName() + " 存了:" + addMoney + " 余额:" + money);
        flag = true;
        this.notify();
    }
    public synchronized void take(double takeMoney) {
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money -= takeMoney;
        System.out.println(Thread.currentThread().getName() + " 取了:" + takeMoney + " 余额:" + money);
        flag = false;
        this.notify();
    }
}
public class AddMoney implements Runnable{
    BankCard bankCard;

    public AddMoney() {
    }

    public AddMoney(BankCard bankCard) {
        this.bankCard = bankCard;
    }

    @Override
    public void run() {
        for(int i = 0; i < 10; i++) {
            bankCard.save(1000);
        }
    }
}
public class TakeMoney implements Runnable{
    BankCard bankCard;

    public TakeMoney() {
    }

    public TakeMoney(BankCard bankCard) {
        this.bankCard = bankCard;
    }

    @Override
    public void run() {
        for(int i = 0; i < 10; i++) {
            bankCard.take(1000);
        }
    }
}
public class BankTest {
    public static void main(String[] args) {
        BankCard bankCard = new BankCard();
        AddMoney addMoney = new AddMoney(bankCard);
        TakeMoney takeMoney = new TakeMoney(bankCard);
        Thread thread1 = new Thread(addMoney, "男");
        Thread thread2 = new Thread(takeMoney, "女");
        thread1.start();
        thread2.start();
    }
}

结果

存取交替执行,没有问题

男 存了:1000.0 余额:1000.0
女 取了:1000.0 余额:0.0
男 存了:1000.0 余额:1000.0
女 取了:1000.0 余额:0.0
……

2、将线程数量增加到 4 个,两个存钱,两个取钱

public class BankTest {
    public static void main(String[] args) {
        BankCard bankCard = new BankCard();
        AddMoney addMoney = new AddMoney(bankCard);
        TakeMoney takeMoney = new TakeMoney(bankCard);
        Thread producerA = new Thread(addMoney, "生产者A");
        Thread producerB = new Thread(takeMoney, "生产者B");
        Thread customerA = new Thread(takeMoney, "消费者A");
        Thread customerB = new Thread(takeMoney, "消费者B");
        producerA.start();
        producerB.start();
        customerA.start();
        customerB.start();
    }
}

结果

结果为乱序执行,而且最终形成死锁

生产者A 存了:1000.0 余额:1000.0
消费者A 取了:1000.0 余额:0.0
生产者A 存了:1000.0 余额:1000.0
生产者B 存了:1000.0 余额:2000.0
......

原因

  • 生产者A抢到了CPU,存钱成功,修改标记为true,唤醒空,余额1000
  • 生产者B抢到了CPU,不能存,进入阻塞队列,释放锁和CPU
  • 生产者A抢到了CPU,不能存,进入阻塞队列,释放锁和CPU
  • 消费者A抢到了CPU,取钱成功,修改标记为false,唤醒生产者A,余额0
  • 生产者A抢到了CPU,存钱成功,修改标记为true,唤醒生产者B,余额1000
  • 生成者B抢到了CPU,存钱成功**(因为此时生产者B处于阻塞中,被唤醒并抢到CPU后不会再判断标记,是否可以存钱,而是直接执行接下来的代码)**,修改标记为true,唤醒空,余额2000

解决

将存钱和取钱的判断语句 if 修改为 while 后

while (flag) {
    try {
        this.wait();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

结果

生产者A 存了:1000.0 余额:1000.0
消费者B 取了:1000.0 余额:0.0
生产者A 存了:1000.0 余额:1000.0
消费者B 取了:1000.0 余额:0.0
————————然后形成了死锁,程序卡在这里…

原因

生产者A抢到CPU,存钱成功,修改标记为true,唤醒空,(阻塞队列:)

生产者B抢到CPU,不能存,进入阻塞队列……释放CPU和锁,(阻塞队列:生产者B)

生产者A抢到CPU,不能存,进入阻塞队列……释放CPU和锁,(阻塞队列:生产者B、生产者A)

消费者A抢到CPU,取钱成功,修改标记为false,唤醒生产者A,(阻塞队列:生产者B)

消费者B抢到CPU,不能取,进入阻塞队列……释放CPU和锁,(阻塞队列:生产者B、消费者B)

消费者A抢到CPU,不能取,进入阻塞队列……释放CPU和锁,(阻塞队列:生产者B、消费者B、消费者A)

生产者A抢到CPU,存钱成功,修改标记为true,释放CPU和锁,唤醒生产者B(阻塞队列:消费者B、消费者A)

生产者B抢到CPU,不能存,进入阻塞队列……释放CPU和锁,(阻塞队列:消费者B、消费者A、生产者B)

生产者A抢到CPU,不能存,进入阻塞队列……释放CPU和锁,(阻塞队列:消费者B、消费者A、生产者B、生产者A)

此时四个线程都进入了阻塞队列中,没有可以唤醒的线程了,所以就形成了死锁

解决

  • 将两个方法中的 notify() 换成 notifyAll(),就可以解决问题了
public synchronized void save(double addMoney) {
    while (flag) {
        try {
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    money += addMoney;
    System.out.println(Thread.currentThread().getName() + " 存了:" + addMoney + " 余额:" + money);
    flag = true;
    this.notifyAll();
}
  • 因为这样每次会唤醒所有线程,所以不会存在唤醒一个不可执行的线程的情况

结果

生产者先存钱,然后消费者取钱,交替执行

生产者A 存了:1000.0 余额:1000.0
消费者B 取了:1000.0 余额:0.0
生产者B 存了:1000.0 余额:1000.0
消费者A 取了:1000.0 余额:0.0
生产者B 存了:1000.0 余额:1000.0
消费者B 取了:1000.0 余额:0.0
......

使用wait-notifyAll实现生产者-消费者

产品类

public class Diamond {
    private Integer id;
    private String manufacturer;

    public Diamond() {
    }

    public Diamond(Integer id, String manufacturer) {
        this.id = id;
        this.manufacturer = manufacturer;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getManufacturer() {
        return manufacturer;
    }

    public void setManufacturer(String manufacturer) {
        this.manufacturer = manufacturer;
    }

    @Override
    public String toString() {
        return "diamond{" +
                "id=" + id +
                ", manufacturer='" + manufacturer + '\'' +
                '}';
    }
}

缓存区工厂,生产、消费功能

public class Factory {
    /**
     * 缓冲区容量
     */
    private final int COPACITY = 5;
    /**
     * 缓存区数组
     */
    private Diamond[] container = new Diamond[COPACITY];
    /**
     * 缓存区内产品数量
     */
    private int size;

    /**
     * 生产产品
     * @param diamond 产品
     */
    public synchronized void put(Diamond diamond) {
        while (size >= COPACITY) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        container[size++] = diamond;
        System.out.println(Thread.currentThread().getName() + " 生产了 " + diamond.getId() + " 号钻石");
        this.notifyAll();
    }

    /**
     * 消费产品
     * @param diamond 产品
     */
    public synchronized void take(Diamond diamond) {
        while (size <= 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        container[--size] = null;
        System.out.println(Thread.currentThread().getName() + " 消费了 " + diamond.getId() + " 号钻石");
        this.notifyAll();
    }
}

生产者:共生产20个产品

public class Producer implements Runnable{
    Factory factory;

    public Producer() {
    }

    public Producer(Factory factory) {
        this.factory = factory;
    }

    @Override
    public void run() {
        for(int i = 0; i < 20; i++) {
            factory.put(new Diamond(i, Thread.currentThread().getName()));
        }
    }
}

消费者:共消费20个产品

public class Customer implements Runnable{
    Factory factory;

    public Customer() {
    }

    public Customer(Factory factory) {
        this.factory = factory;
    }

    @Override
    public void run() {
        for(int i = 0; i < 20; i++) {
            factory.take(new Diamond(i, Thread.currentThread().getName()));
        }
    }
}

测试

两个生产者,两个消费者同时执行

public class DiamondTest {
    public static void main(String[] args) {
        Factory factory = new Factory();
        Producer producer = new Producer(factory);
        Customer customer = new Customer(factory);

        Thread producerA = new Thread(producer, "生产者A");
        Thread producerB = new Thread(producer, "生产者B");
        Thread customerA = new Thread(customer, "消费者A");
        Thread customerB = new Thread(customer, "消费者B");
        producerA.start();
        producerB.start();
        customerA.start();
        customerB.start();
    }
}

结果

结果正确,当产品没有满的时候可以生产,当产品没有空的时候可以消费

生产者A 生产了 0 号钻石
生产者A 生产了 1 号钻石
生产者A 生产了 2 号钻石
生产者A 生产了 3 号钻石
生产者A 生产了 4 号钻石
消费者B 消费了 0 号钻石
消费者B 消费了 1 号钻石
消费者B 消费了 2 号钻石
......

使用LinkedBlockingQueue实现生产者-消费者

package com.robot.juc.lock;

import java.util.concurrent.LinkedBlockingQueue;

/**
 * 使用LinkedBlockingQueue实现生产者-消费者。
 *
 * @author 张宝旭
 */
public class LinkedBlockingQueueTest {
    // 创建阻塞队列
    LinkedBlockingQueue<String> queue  = new LinkedBlockingQueue<>(10);

    public static void main(String[] args) {
        LinkedBlockingQueueTest test = new LinkedBlockingQueueTest();
        new Thread(test::producer).start();
        new Thread(test::consumer).start();
    }

    /**
     * 生产者
     */
    public void producer() {
        try {
            for(int i = 0; i < 10; i++) {
                queue.put(Thread.currentThread().getName() + " : " + i);
                System.out.println(Thread.currentThread().getName() + " 生产了: " + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 消费者
     */
    public void consumer() {
        try {
            for(int i = 0; i < 10; i++) {
                queue.take();
                System.out.println(Thread.currentThread().getName() + " 消费了: " + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值