问题描述:生产者与消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个(多个)共享固定大小缓冲区的线程
——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
生产者与消费者问题中其实隐含了两个问题:
- 线程安全问题:因为生产者与消费者共享数据缓冲区,产生安全问题。不过这个问题可以使用同步解决。
- 线程的协调工作问题:
- 要解决该问题,就必须让生产者线程在缓冲区满时等待(wait),暂停进入阻塞状态,等到下次消费者消耗了缓冲区中的数据的时候,通知(notify)正在等待的线程恢复到就绪状态,重新开始往缓冲区添加数据。同样,也可以让消费者线程在缓冲区空时进入等待(wait),暂停进入阻塞状态,等到生产者往缓冲区添加数据之后,再通知(notify)正在等待的线程恢复到就绪状态。通过这样的通信机制来解决此类问题。
方式一:使用同步方法+线程通信(notify和wait方法)实现
public class ProducerConsumerDemo {
public static void main(String[] args) {
Clerk clerk = new Clerk();
ProducerThread producerThread = new ProducerThread(clerk);
producerThread.setName("生产者A");
CounsumberThread counsumberThread1 = new CounsumberThread(clerk);
counsumberThread1.setName("消费者A");
CounsumberThread counsumberThread2 = new CounsumberThread(clerk);
counsumberThread2.setName("消费者B");
//线程就绪
producerThread.start();
counsumberThread1.start();
counsumberThread2.start();
}
}
class Clerk {
//当前产品数量,初始值为0
public static int currentCount = 0;
//最多产品数量
public static final int MAX_COUNT = 20;
//最少产品数量
public static final int MIN_COUNT = 1;
/**
* 生成一个商品
*/
public synchronized void addAProduct() {
//产品数量未到上限
if (currentCount < MAX_COUNT) {
//产品数量+1
Clerk.currentCount++;
System.out.println(LocalDateTime.now() + "-----" + Thread.currentThread().getName() + "生产了" + Clerk.currentCount + "号产品");
//唤醒沉睡的消费者线程
notifyAll();
} else {
try {
//产品数量达到上限,生产者线程需要阻塞,等待消费者线程唤醒
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
/**
* 消费1个商品
*/
public synchronized void subAProduct() {
//产品数量不为0
if (currentCount >= MIN_COUNT) {
//产品数量-1
System.out.println(LocalDateTime.now() + "-----" + Thread.currentThread().getName() + "消费了" + Clerk.currentCount + "号产品");
Clerk.currentCount--;
//唤醒沉睡的生产者线程
notifyAll();
} else {
try {
//产品数量为0,消费者线程需要阻塞,等待生产者线程唤醒
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
class ProducerThread extends Thread {
private Clerk clerk;
public ProducerThread(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
try {
//随机沉睡时间,范围[1-50]
Thread.sleep((int) (Math.random() * 50) + 1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
clerk.addAProduct();
}
}
}
class CounsumberThread extends Thread {
private Clerk clerk;
public CounsumberThread(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
try {
//随机沉睡时间,范围[1-100]
Thread.sleep((int) (Math.random() * 100) + 1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
clerk.subAProduct();
}
}
}
补充:可以使用Lock锁来解决上述问题
public class ProducerConsumerDemo {
public static void main(String[] args) {
Clerk clerk = new Clerk();
ProducerThread producerThread = new ProducerThread(clerk);
producerThread.setName("生产者A");
CounsumberThread counsumberThread1 = new CounsumberThread(clerk);
counsumberThread1.setName("消费者A");
CounsumberThread counsumberThread2 = new CounsumberThread(clerk);
counsumberThread2.setName("消费者B");
//线程就绪
producerThread.start();
counsumberThread1.start();
counsumberThread2.start();
}
}
class Clerk {
//当前产品数量,初始值为0
public static int currentCount = 0;
//最多产品数量
public static final int MAX_COUNT = 20;
//最少产品数量
public static final int MIN_COUNT = 1;
//利用Lock来控制对共享资源的访问
ReentrantLock lock = new ReentrantLock();
/**
* 生成一个商品
*/
public void addAProduct() {
try {
lock.lock();
//产品数量未到上限
if (currentCount < MAX_COUNT) {
//产品数量+1
Clerk.currentCount++;
System.out.println(LocalDateTime.now() + "-----" + Thread.currentThread().getName() + "生产了" + Clerk.currentCount + "号产品");
}
}finally {
lock.unlock();
}
}
/**
* 消费1个商品
*/
public void subAProduct() {
try {
lock.lock();
//产品数量不为0
if (currentCount >= MIN_COUNT) {
//产品数量-1
System.out.println(LocalDateTime.now() + "-----" + Thread.currentThread().getName() + "消费了" + Clerk.currentCount + "号产品");
Clerk.currentCount--;
}
}finally {
lock.unlock();
}
}
}
class ProducerThread extends Thread {
private Clerk clerk;
public ProducerThread(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
try {
//随机沉睡时间,范围[1-50]
Thread.sleep((int) (Math.random() * 50) + 1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
clerk.addAProduct();
}
}
}
class CounsumberThread extends Thread {
private Clerk clerk;
public CounsumberThread(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
try {
//随机沉睡时间,范围[1-100]
Thread.sleep((int) (Math.random() * 100) + 1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
clerk.subAProduct();
}
}
}