目录
注意:本文参考 【Java总结】生产者消费者模型_zhujohnle的专栏-CSDN博客
消费者读取处理慢_Java高并发之Disruptor框架:单线程每秒处理600万订单高并发框架..._weixin_29628611的博客-CSDN博客
生产者消费者模型简介
生产者消费者模型主要结构如下,是一个典型的线程同步的案例。下面就来使用java做几种线程同步的方式来实现以下该模型
确保一个生产者消费者模型的稳定运行的前提有以下几个
生成者应该具备持续生成的能力
消费者应该具备持续消费的能力
生产者的生成和消费消费有一定的阀值,如生成总量到100需要停止生产,通知消费;消费到0的时候停止消费开始生产;
wait,notify方案
wait,notify方案 主要是通过,使用对象的wait方法和notify方法来实现线程的切换执行。其中我们可以看到对象的wait和notify或者notifyAll方法都是调用native的对应的方法来处理,追溯到最后也还是控制cpu进行不同的时间片的切换
下面这个例子比较简单,模拟一个生产速度大于消费速度的这样一个案例,在生产到阀值的时候停止生产通知消费者进行消费(wait)。消费者在消费到一定阀值的时候停止消费通知生产者进行生产(notifyall)
public class TestWaitNotifyConsumerAndProducer {
/*当前生成数量*/
static int currentNum = 0;
/*最大生成数量*/
static int MAX_NUM = 10;
/*最小消费数量*/
static int MIN_NUM = 0;
/*wait和notify控制对象*/
private static final String lock = "lock";
public static void main(String args[]) {
//创建一个生产者
new Thread(new Producer()).start();
//创建两个消费者
new Thread(new Consumer()).start();
new Thread(new Consumer()).start();
}
static class Producer implements Runnable {
public void product() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
currentNum++;
System.out.println("Producer now product num:" + currentNum);
lock.notifyAll();
if (currentNum == MAX_NUM) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
@Override
public void run() {
product();
}
}
static class Consumer implements Runnable {
public void consume() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
if (currentNum == MIN_NUM) {
lock.notifyAll();
continue;
}
System.out.println(new StringBuilder(Thread.currentThread().getName())
.append(" Consumer now consumption num:").append(currentNum));
currentNum--;
}
}
}
@Override
public void run() {
consume();
}
}
}
public class ProducerConsumer1 {
class Producer extends Thread {
private String threadName;
private Queue<Goods> queue;
private int maxSize;
public Producer(String threadName, Queue<Goods> queue, int maxSize) {
this.threadName = threadName;
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run() {
while (true) {
//模拟生产过程中的耗时操作
Goods goods = new Goods();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (queue) {
while (queue.size() == maxSize) {
try {
System.out.println("队列已满,【" + threadName + "】进入等待状态");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.add(goods);
System.out.println("【" + threadName + "】生产了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size());
queue.notifyAll();
}
}
}
}
class Consumer extends Thread {
private String threadName;
private Queue<Goods> queue;
public Consumer(String threadName, Queue<Goods> queue) {
this.threadName = threadName;
this.queue = queue;
}
@Override
public void run() {
while (true) {
Goods goods;
synchronized (queue) {
while (queue.isEmpty()) {
try {
System.out.println("队列已空,【" + threadName + "】进入等待状态");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
goods = queue.remove();
System.out.println("【" + threadName + "】消费了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size());
queue.notifyAll();
}
//模拟消费过程中的耗时操作
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test() {
int maxSize = 5;
Queue<Goods> queue = new LinkedList<>();
Thread producer1 = new Producer("生产者1", queue, maxSize);
Thread producer2 = new Producer("生产者2", queue, maxSize);
Thread producer3 = new Producer("生产者3", queue, maxSize);
Thread consumer1 = new Consumer("消费者1", queue);
Thread consumer2 = new Consumer("消费者2", queue);
producer1.start();
producer2.start();
producer3.start();
consumer1.start();
consumer2.start();
while (true) {
}
}
}
1 确定锁的对象是队列queue
;
2 不要把生产过程和消费过程写在同步块中,这些操作无需同步,同步的仅仅是放入和取出这两个动作;
3 因为是持续生产,持续消费,要用while(true){...}
的方式将【生产、放入】或【取出、消费】的操作都一直进行。
4 但由于是对队列使用synchronized
的方式加锁,同一时刻,要么在放入,要么在取出,两者不能同时进行。
ReentrantLock的实现
ReentrantLock 也是java.util.concurrent中显示锁的一种,允许同一个线程重复进入一段执行代码(递归),并且反复lock加锁。重复加锁相当于计数器累加,因此当一个线程想释放这个锁的时候就需要有对应的unlock执行。
如下这段代码依然是我们开篇讲到的生成和消费的模型,只是换了一种实现;这里特别注意的是可重入锁或者递归锁需要成对的出现lock和unlock否则执行Condition的await 或者signal的时候就可能如下抛出
java.lang.IllegalMonitorStateException