Java阻塞队列和非阻塞队列
1. 什么是阻塞队列?
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。
这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
JDK7 提供了 7 个阻塞队列。分别是:
-
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
-
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
-
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
-
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
-
SynchronousQueue:一个不存储元素的阻塞队列。
-
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
-
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
Java 5 之前实现同步存取时,可以使用普通的一个集合,然后在使用线程的协作和线程同步可以实现生产者,消费者模式,主要的技术就是用好,wait,notify,notifyAll,sychronized 这些关键字。而在 java 5 之后,可以使用阻塞队列来实现,此方式大大简少了代码量,使得多线程编程更加容易,安全方面也有保障。
BlockingQueue 接口是 Queue 的子接口,它的主要用途并不是作为容器,而是作为线程同步的的工具,因此他具有一个很明显的特性,当生产者线程试图向 BlockingQueue 放入元素时,如果队列已满,则线程被阻塞,当消费者线程试图从中取出一个元素时,如果队列为空,则该线程会被阻塞,正是因为它所具有这个特性,所以在程序中多个线程交替向 BlockingQueue 中放入元素,取出元素,它可以很好的控制线程之间的通信。
阻塞队列使用最经典的场景就是 socket 客户端数据的读取和解析,读取数据的线程不断将数据放入队列,然后解析线程不断从队列取数据解析。
2. 阻塞队列的生产者-消费者模式
阻塞队列(Blocking queue)提供了可阻塞的put和take方法,它们与可定时的offer和poll是等价的。如果Queue已经满了,put方法会被阻塞直到有空间可用;如果Queue是空的,那么take方法会被阻塞,直到有元素可用。Queue的长度可以有限,也可以无限;无限的Queue永远不会充满,所以它的put方法永远不会阻塞。
阻塞队列支持生产者-消费者设计模式。一个生产者-消费者设计分离了“生产产品”和“消费产品”。该模式不会发现一个工作便立即处理,而是把工作置于一个任务(“to do”)清单中,以备后期处理。生产者-消费者模式简化了开发,因为它解除了生产者和消费者之间相互依赖的代码。生产者和消费者以不同的或者变化的速度生产和消费数据,生产者-消费者模式将这些活动解耦,因而简化了工作负荷的管理。
生产者-消费者设计是围绕阻塞队列展开的,生产者把数据放入队列,并使数据可用,当消费者为适当的行为做准备时会从队列中获取数据。生产者只负责把数据放入队列。类似地,消费者也不需要知道生产者是谁,以及是谁给它们安排的工作。BlockingQueue可以使用任意数量的生产者和消费者,从而简化了生产者-消费者设计的实现。最常见的生产者-消费者设计是将线程池与工作队列相结合。
3. 有哪些非阻塞队列?
- ArrayQueue:数组队列
- ArrayDeque:数组双端队列
- PriorityQueue:优先级队列
- ConcurrentLinkedQueue:基于链表的并发队列
4. 使用阻塞队列和非阻塞队列的生产者-消费者模式代码
场景:1个生产者线程,3个消费者线程,工作队列的大小为2
4.1 阻塞队列(LinkedBlockingQueue实现)
package com.feng.queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Created by FengBin on 2021/8/17 14:20
* 基于阻塞队列实现的生产者-消费者模式
*/
public class TestBlockingQueue {
private int queueSize = 2; //生产者的队列大小
private BlockingQueue<Integer> workQueue = new LinkedBlockingQueue<Integer>(queueSize);
public static void main(String[] args) throws InterruptedException {
TestBlockingQueue solution = new TestBlockingQueue();
Producer producer1 = solution.new Producer();
Consumer consumer1 = solution.new Consumer();
Consumer consumer2 = solution.new Consumer();
Consumer consumer3 = solution.new Consumer();
producer1.start();
consumer1.start();
consumer2.start();
consumer3.start();
}
//定义一个生产者
class Producer extends Thread {
@Override
public void run() {
produce();
}
private void produce() {
String name = Thread.currentThread().getName() + "-producer";
while (true) {
try {
Thread.sleep(2000);
workQueue.put(1);
System.out.println("[" + name + "]" + "向队列中生产一个元素,队列的剩余空间为:" + (queueSize - workQueue.size()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//定义一个消费者
class Consumer extends Thread {
@Override
public void run() {
consume();
}
public void consume() {
String name = Thread.currentThread().getName() + "-consumer";
while (true) {
try {
System.out.println("[" + name + "]" + "准备从队列中消费元素");
workQueue.take();
System.out.println("[" + name + "]" + "从队列中消费一个元素,队列的剩余空间为:" + (queueSize - workQueue.size()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
输出结果:
[Thread-2-consumer]准备从队列中消费元素
[Thread-1-consumer]准备从队列中消费元素
[Thread-3-consumer]准备从队列中消费元素
[Thread-0-producer]向队列中生产一个元素,队列的剩余空间为:1
[Thread-2-consumer]从队列中消费一个元素,队列的剩余空间为:2
[Thread-2-consumer]准备从队列中消费元素
[Thread-0-producer]向队列中生产一个元素,队列的剩余空间为:1
[Thread-1-consumer]从队列中消费一个元素,队列的剩余空间为:2
[Thread-1-consumer]准备从队列中消费元素
[Thread-0-producer]向队列中生产一个元素,队列的剩余空间为:1
[Thread-3-consumer]从队列中消费一个元素,队列的剩余空间为:2
[Thread-3-consumer]准备从队列中消费元素
[Thread-0-producer]向队列中生产一个元素,队列的剩余空间为:1
[Thread-2-consumer]从队列中消费一个元素,队列的剩余空间为:2
[Thread-2-consumer]准备从队列中消费元素
[Thread-0-producer]向队列中生产一个元素,队列的剩余空间为:1
[Thread-1-consumer]从队列中消费一个元素,队列的剩余空间为:2
[Thread-1-consumer]准备从队列中消费元素
[Thread-0-producer]向队列中生产一个元素,队列的剩余空间为:1
[Thread-3-consumer]从队列中消费一个元素,队列的剩余空间为:2
[Thread-3-consumer]准备从队列中消费元素
[Thread-0-producer]向队列中生产一个元素,队列的剩余空间为:1
[Thread-2-consumer]从队列中消费一个元素,队列的剩余空间为:2
[Thread-2-consumer]准备从队列中消费元素
[Thread-0-producer]向队列中生产一个元素,队列的剩余空间为:1
[Thread-1-consumer]从队列中消费一个元素,队列的剩余空间为:2
[Thread-1-consumer]准备从队列中消费元素
[Thread-0-producer]向队列中生产一个元素,队列的剩余空间为:1
[Thread-3-consumer]从队列中消费一个元素,队列的剩余空间为:2
[Thread-3-consumer]准备从队列中消费元素
可以看出,只有当队列中存在元素,消费者线程才会去队列中取元素,否则将会阻塞,直到队列中生产了元素以后才会继续执行
4.2 非阻塞队列(ConcurrentLinkedQueue实现)
package com.feng.queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Created by FengBin on 2021/8/17 14:47
*/
public class TestConcurrentLinkedQueue {
private ConcurrentLinkedQueue<Integer> workQueue = new ConcurrentLinkedQueue<>();
public static void main(String[] args) {
TestConcurrentLinkedQueue solution = new TestConcurrentLinkedQueue();
Producer producer1 = solution.new Producer();
Consumer consumer1 = solution.new Consumer();
Consumer consumer2 = solution.new Consumer();
Consumer consumer3 = solution.new Consumer();
producer1.start();
consumer1.start();
consumer2.start();
consumer3.start();
}
//定义一个生产者
class Producer extends Thread {
@Override
public void run() {
produce();
}
private void produce() {
String name = Thread.currentThread().getName() + "-producer";
while (true) {
try {
Thread.sleep(2000);
workQueue.offer(1);
System.out.println("[" + name + "]" + "向队列中生产一个元素");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//定义一个消费者
class Consumer extends Thread {
@Override
public void run() {
consume();
}
public void consume() {
String name = Thread.currentThread().getName() + "-consumer";
while (true) {
try {
Thread.sleep(1000);
System.out.println("[" + name + "]" + "准备从队列中消费元素");
Integer poll = workQueue.poll();
System.out.println("[" + name + "]" + "从队列中消费元素:" + poll);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
输出结果:
[Thread-2-consumer]准备从队列中消费元素
[Thread-3-consumer]准备从队列中消费元素
[Thread-2-consumer]从队列中消费元素:null
[Thread-1-consumer]准备从队列中消费元素
[Thread-3-consumer]从队列中消费元素:null
[Thread-1-consumer]从队列中消费元素:null
[Thread-0-producer]向队列中生产一个元素
[Thread-2-consumer]准备从队列中消费元素
[Thread-1-consumer]准备从队列中消费元素
[Thread-1-consumer]从队列中消费元素:null
[Thread-3-consumer]准备从队列中消费元素
[Thread-3-consumer]从队列中消费元素:null
[Thread-2-consumer]从队列中消费元素:1
[Thread-2-consumer]准备从队列中消费元素
[Thread-2-consumer]从队列中消费元素:null
[Thread-3-consumer]准备从队列中消费元素
[Thread-3-consumer]从队列中消费元素:null
[Thread-1-consumer]准备从队列中消费元素
[Thread-1-consumer]从队列中消费元素:null
[Thread-0-producer]向队列中生产一个元素
[Thread-1-consumer]准备从队列中消费元素
[Thread-1-consumer]从队列中消费元素:1
...
使用非阻塞队列,当消费者线程从队列中获取元素,而此时队列为空,则不会阻塞,而是返回null,即获取到null
如果使用ConcurrentLinkedQueue作为生产者-消费者模型的队列使用,应该加上锁的并发控制机制