一、问题描述
生产者消费者问题(Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例。生产者生成一定量的数据放到缓冲区中,然后重复此过程;与此同时,消费者也在缓冲区消耗这些数据。生产者和消费者之间必须保持同步,要保证生产者不会在缓冲区满时放入数据,消费者也不会在缓冲区空时消耗数据。不够完善的解决方法容易出现死锁的情况,此时进程都在等待唤醒。
所谓生产者消费者模式,即N个线程进行生产,同时N个线程进行消费,两种角色通过内存缓冲区进行通信
二、解决思路
1. 采用某种机制保护生产者和消费者之间的同步。有较高的效率,并且易于实现,代码的可控制性较好,属于常用的模式。
2. 在生产者和消费者之间建立一个管道。管道缓冲区不易控制,被传输数据对象不易于封装等,实用性不强。
保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用信号或加锁机制,保证资源在任意时刻至多被一个线程访问。
Java能实现的几种方法
1. 用synchronized对存储加锁,然后用object原生的wait() 和 notify()做同步。
2. 用concurrent.locks.Lock,然后用condition的await() 和signal()做同步。
3. 直接使用concurrent.BlockingQueue。
4. 使用信号量semaphore。
5. 使用PipedInputStream/PipedOutputStream。(没有实现)
三、代码实现
1. 用synchronized对存储加锁,然后用object原生的wait() 和 notify()做同步 实现
用synchronized对存储加锁,然后用object原生的wait() 和 notify()做同步。
当缓冲区已满时,生产者线程停止执行,放弃锁,使自己处于等状态,让其他线程执行;
当缓冲区已空时,消费者线程停止执行,放弃锁,使自己处于等状态,让其他线程执行。
当生产者向缓冲区放入一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态;
当消费者从缓冲区取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。
package com.ryz2593.happy.study.producer_consumer.synchronized_wait_notify;
import com.google.common.collect.Lists;
import java.util.LinkedList;
/**
* @author ryz2593
*/
public class Storage {
/**
* 仓库容量
*/
private final int MAX_SIZE = 10;
/**
* 仓库存储的载体
*/
private LinkedList<Object> list = Lists.newLinkedList();
public void produce() {
synchronized (list) {
while (list.size() + 1 > MAX_SIZE) {
System.out.println("[producer" + Thread.currentThread().getName()
+ "] storage is full");
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(new Object());
System.out.println("[Consumer" + Thread.currentThread().getName()
+ "]生产一个产品,现库存" + list.size());
list.notifyAll();
}
}
public void consumer() {
synchronized (list) {
while (list.size() == 0) {
System.out.println("[Consumer" + Thread.currentThread().getName()
+ "] storage is empty");
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.remove();
System.out.println("[Consumer" + Thread.currentThread().getName()
+ "]消费一个产品,现库存" + list.size());
list.notifyAll();
}
}
}
Producer.java
package com.ryz2593.happy.study.producer_consumer.synchronized_wait_notify;
public class Producer implements Runnable {
private Storage storage;
public Producer() {
}
public Producer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
storage.produce();
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
Consumer.java
package com.ryz2593.happy.study.producer_consumer.synchronized_wait_notify;
public class Consumer implements Runnable {
private Storage storage;
public Consumer() {
}
public Consumer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
storage.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Main.java
public class Main {
public static void main(String[] args) {
Storage storage = new Storage();
Thread p1 = new Thread(new Producer(storage));
Thread p2 = new Thread(new Producer(storage));
Thread p3 = new Thread(new Producer(storage));
Thread c1 = new Thread(new Consumer(storage));
Thread c2 = new Thread(new Consumer(storage));
Thread c3 = new Thread(new Consumer(storage));
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
c3.start();
}
}
运行结果:
[ConsumerThread-5] storage is empty
[ConsumerThread-4] storage is empty
[ConsumerThread-2]生产一个产品,现库存1
[ConsumerThread-4]消费一个产品,现库存0
[ConsumerThread-5] storage is empty
[ConsumerThread-3] storage is empty
[ConsumerThread-0]生产一个产品,现库存1
[ConsumerThread-3]消费一个产品,现库存0
[ConsumerThread-5] storage is empty
[ConsumerThread-1]生产一个产品,现库存1
[ConsumerThread-5]消费一个产品,现库存0
[ConsumerThread-2]生产一个产品,现库存1
[ConsumerThread-0]生产一个产品,现库存2
[ConsumerThread-4]消费一个产品,现库存1
[ConsumerThread-3]消费一个产品,现库存0
[ConsumerThread-5] storage is empty
[ConsumerThread-1]生产一个产品,现库存1
[ConsumerThread-5]消费一个产品,现库存0
2. 用concurrent.locks.Lock,然后用condition的await() 和signal()做同步
package com.ryz2593.happy.study.producer_consumer.lock_await_signal;
import com.google.common.collect.Lists;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author ryz2593
*/
public class Storage {
private final int MAX_SIZE = 10;
private LinkedList<Object> list = Lists.newLinkedList();
private final Lock lock = new ReentrantLock();
private final Condition full = lock.newCondition();
private final Condition empty = lock.newCondition();
public void produce() {
lock.lock();
while (list.size() + 1 > MAX_SIZE) {
System.out.println("[Producer" + Thread.currentThread().getName() + "]仓库已满");
try {
full.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(new Object());
System.out.println("[Producer" + Thread.currentThread().getName() + "]生产一个产品,现库存" + list.size());
empty.signalAll();
lock.unlock();
}
public void consumer() {
lock.lock();
while (list.size() == 0) {
System.out.println("[Consumer" + Thread.currentThread().getName()
+ "]仓库为空");
try {
empty.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.remove();
System.out.println("[Consumer" + Thread.currentThread().getName()
+ "]消费一个产品,现库存" + list.size());
full.signalAll();
lock.unlock();
}
}
3. 直接使用concurrent.BlockingQueue。
BlockingQueue是JDK5.0的新增内容,它是一个已经在内部实现了同步的队列,实现方式采用的是我们第2种await() / signal()方法。它可以在生成对象时指定容量大小,用于阻塞操作的是put()和take()方法。
put()方法:类似于我们上面的生产者线程,容量达到最大时,自动阻塞。
take()方法:类似于我们上面的消费者线程,容量为0时,自动阻塞。
package com.ryz2593.happy.study.producer_consumer.blockingqueue;
import java.util.concurrent.LinkedBlockingDeque;
/**
* @author ryz2593
*/
public class Storage {
/**
* 仓库存储的载体
*/
private LinkedBlockingDeque<Object> list = new LinkedBlockingDeque<>(10);
public void producer() {
try {
list.put(new Object());
System.out.println("[Producer" + Thread.currentThread().getName()
+ "]生产一个产品,现库存" + list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void consumer() {
try {
list.take();
System.out.println("[Consumer" + Thread.currentThread().getName()
+ "]消费了一个产品,现库存" + list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4. 使用信号量semaphore。
Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。计数为0的Semaphore是可以release的,然后就可以acquire(即一开始使线程阻塞从而完成其他执行。)。
package com.ryz2593.happy.study.producer_consumer.semaphore;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Semaphore;
/**
* @author ryz2593
*/
public class Storage {
List<Integer> buffer = Lists.newLinkedList();
/**
* // 互斥量,控制buffer的互斥访问
*/
private Semaphore mutex = new Semaphore(1);
/**
* // canProduceCount可以生产的数量(表示缓冲区可用的数量)。 通过生产者调用acquire,减少permit数目
*/
private Semaphore canProduceCount = new Semaphore(10);
/**
* // canConsumerCount可以消费的数量。通过生产者调用release,增加permit数目
*/
private Semaphore canConsumerCount = new Semaphore(0);
Random rn = new Random(10);
public void get() throws InterruptedException {
canConsumerCount.acquire();
try {
mutex.acquire();
int val = buffer.remove(0);
System.out.println(Thread.currentThread().getName() + " 正在消费数据为:" + val + " buffer目前大小为:" + buffer.size());
} finally {
mutex.release();
canProduceCount.release();
}
}
public void put() throws InterruptedException {
canProduceCount.acquire();
try {
mutex.acquire();
int val = rn.nextInt(10);
buffer.add(val);
System.out.println(Thread.currentThread().getName() + " 正在生产数据为:" + val + " buffer目前大小为:" + buffer.size());
} finally {
mutex.release();
// 生产者调用release,增加可以消费的数量
canConsumerCount.release();
}
}
}
参考:
https://blog.csdn.net/ldx19980108/article/details/81707751
https://blog.csdn.net/xindoo/article/details/80004003
https://blog.csdn.net/qq_33006397/article/details/82657596