内容
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();
}
}
}