一、什么是生产者消费者模型
生产者与消费者模式就是一个多线程并发协作的模式,在这个模式中呢,一部分线程被用于去生产数据,另一部分线程去处理数据,于是便有了形象的生产者与消费者了。而为了更好的优化生产者与消费者的关系,便设立一个缓冲区,也就相当于一个数据仓库,当生产者生产数据时锁住仓库,不让消费者访问,当消费者消费时锁住仓库,不让生产者访问仓库。举一个简单的例子,有一个生产者生产包子,他将生产好的包子放到筐中,放完包子由消费者从筐中拿出包子使用。当然筐还有一个作用就是当筐中没有包子时便锁住筐,不让消费者去筐中再拿取东西,当筐中有包子时,不让生产者再向筐中放入包子。
二.代码实现
1.基于await()/signal()与可重入锁的代码
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by WanYue
*/
public class ProducerCustomerModel {
//维护一个临界区域
private Queue<Integer> queue = new LinkedList<Integer>();
//区域的最大值
final static int MAX = 10;
ReentrantLock lock = new ReentrantLock();
//表示队列满了
Condition full = lock.newCondition();
//表示队列是空的
Condition empty = lock.newCondition();
int readData() throws InterruptedException {
Thread.sleep((long)Math.random()*1000);
return (int) Math.floor(Math.random());
}
//Producer
public void readDb() throws InterruptedException{
//必须锁住不是一下子就超过10了 就死锁了。
lock.lock();
// synchronized (queue){
//满了就不生产了
if(queue.size() == MAX){
//monitor休眠方法
// queue.wait();
//condition的指定休眠
full.await();
return;
}
//如果存在一个就唤醒消费者来消费
if(queue.size() == 1) empty.signalAll();
//没有满 就去生产
int data = readData();
queue.add(data);
lock.unlock();
// }
}
//Consumer,它的作用是计算
public void calculate() throws InterruptedException{
lock.lock();
// synchronized (queue){
//如果队列没有东西,就无法消费
if(queue.size() == 0){
// queue.wait();
//condition的指定休眠
empty.await();
return;
}
//只要有一个空位就让生产者来生产
if(queue.size() == MAX - 1) full.signalAll();
Integer data = queue.remove();
System.out.println("queue-size:" + queue.size());
data *= 100;
// }
lock.unlock();
}
/**
* 直接用唤醒notify会被卡死
* P0 P1
* C0
* P0 C0 (都处于sleeping) C0 -> notify(p0 丨 p1)
*P1 执行后queue.size() == 1 ,唤醒了P0 ,P1,把生产的放入queue,size=2了, P0就开始生产,不会执行了notify,因为不可能等于1了,因此
* 需要notifyAll()都要将其唤醒 强行一起唤醒,
* 需要改进的, 用一个两个Condition解决full,empty
*/
public static void main(String[] args) {
ProducerCustomerModel p = new ProducerCustomerModel();
//开100个线程开始生产,生产一般都比消费慢
for (int i = 0; i < 100; i++) {
new Thread(() -> {
while(true){
try {
p.readDb();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
//一个消费者就可以了
new Thread(() -> {
//消费者一直来回去看是否有可以消费的没。 没有while只会执行一次
while(true){
try {
p.calculate();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
2.基于阻塞队列的take()与put()的实现
实际上阻塞队列的底层还是ReentLock
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by WanYue
*/
public class BlockingQueueTest {
public static void main(String[] args) {
//记录取的次数
AtomicInteger count = new AtomicInteger();
BlockingQueue<Integer> queue;
//queue = new LinkedBlockingQueue<>(10); //前10个有效,后面全是Null
queue = new ArrayBlockingQueue<>(10);
// queue = new LinkedBlockingDeque<>();
// queue = new PriorityBlockingQueue<>();
// queue = new SynchronousQueue<>();
// queue = new DelayQueue<Integer>();
//Producer,开30个线程去生产,说明只是生产30个
for (int i = 0; i < 30; i++) {
new Thread(() -> {
try {
//如果想一直生产那就这里用一个死循环即可
queue.put((int) (Math.random() * 1_000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
//Consumer,10个线程消费
for (int i = 0; i < 10; i++) {
new Thread(() -> {
//一直轮询去访问
while(true){
//开始消费
Integer x = null;
try {
x = (Integer) queue.take();
count.getAndIncrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Receive: " + x +" count:" + count);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}