1.阻塞队列概念
BlockingQueue,是java.util.concurrent 包提供的用于解决并发生产者 - 消费者问题的最有用的类,它的特性是在任意时刻只有一个线程可以进行take或者put操作,并且BlockingQueue提供了超时return null的机制,在许多生产场景里都可以看到这个工具的身影。常见的四种阻队列
ArrayBlockingQueue //由数组支持的有界队列
LinkedBlockingQueue //由链接节点支持的可选有界队列
PriorityBlockingQueue //由优先级堆支持的无界优先级队列
DelayQueue// 由优先级堆支持的、基于时间的调度队列
2.四种阻塞队列详解
ArrayBlockingQueue:
LinkedBlockingQueue:
向无限队列添加元素的所有操作都将永远不会阻塞,[注意这里不是说不会加锁保证线程安全],因此它可以增长到非常大的容量。
使用无限 BlockingQueue 设计生产者 - 消费者模型时最重要的是 消费者应该能够像生产者向队列添加消息一样快地消费消息 。否则,内存可能会填满,然后就会得到一个 OutOfMemory 异常。
DelayQueue:
源码解释(生产者消费者模型)
示例代码:
@Slf4j
public class NumbersProducer implements Runnable {
private BlockingQueue<Integer> numbersQueue;
private final int poisonPill;
private final int poisonPillPerProducer;
public NumbersProducer(BlockingQueue<Integer> numbersQueue, int poisonPill, int poisonPillPerProducer) {
this.numbersQueue = numbersQueue;
this.poisonPill = poisonPill;
this.poisonPillPerProducer = poisonPillPerProducer;
}
public void run() {
try {
generateNumbers();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void generateNumbers() throws InterruptedException {
for (int i = 0; i < 100; i++) {
numbersQueue.put(ThreadLocalRandom.current().nextInt(100));
log.info("潘金莲-{}号,给武大郎的泡药!",Thread.currentThread().getId());
}
for (int j = 0; j < poisonPillPerProducer; j++) {
numbersQueue.put(poisonPill);
log.info("潘金莲-{}号,往武大郎的药里放入第{}颗毒丸!",Thread.currentThread().getId(),j+1);
}
}
}
@Slf4j
public class NumbersConsumer implements Runnable {
private BlockingQueue<Integer> queue;
private final int poisonPill;
public NumbersConsumer(BlockingQueue<Integer> queue, int poisonPill) {
this.queue = queue;
this.poisonPill = poisonPill;
}
public void run() {
try {
while (true) {
Integer number = queue.take();
if (number.equals(poisonPill)) {
return;
}
log.info("武大郎-{}号,喝药-编号:{}",Thread.currentThread().getId(),number);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class Main {
public static void main(String[] args) {
int BOUND = 10;
int N_PRODUCERS = 16;
int N_CONSUMERS = Runtime.getRuntime().availableProcessors();
int poisonPill = Integer.MAX_VALUE;
int poisonPillPerProducer = N_CONSUMERS / N_PRODUCERS;
int mod = N_CONSUMERS % N_PRODUCERS;
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(BOUND);
//潘金莲给武大郎熬药
for (int i = 1; i < N_PRODUCERS; i++) {
new Thread(new NumbersProducer(queue, poisonPill, poisonPillPerProducer)).start();
}
//武大郎开始喝药
for (int j = 0; j < N_CONSUMERS; j++) {
new Thread(new NumbersConsumer(queue, poisonPill)).start();
}
//潘金莲开始投毒,武大郎喝完毒药GG
new Thread(new NumbersProducer(queue, poisonPill, poisonPillPerProducer + mod)).start();
}
}
生产者们和消费们同时竞争队列的使用权(加锁)
生产者向队列中添加元素
首先AQS加锁,如果当前的入队的元素已经达到了阻塞队列的边界,则该生产者(线程)进入条件等在状态,等到队列不满的时候继续入队
notfull.wait()
进入条件队列后,判断线程是否存在中断,抛出异常。park唤醒另一个条件等待队列中的结点。
消费者消费
和生产者类似,先加锁,队列为空时,停止消费,进入条件阻塞队列,
同时唤醒其他条件阻塞队列中的线程
附图解释:
如存在不正确之处,欢迎各位大佬指正