实现Java 阻塞队列
在自己实现之前先搞清楚阻塞队列的几个特点:
基本队列特性:先进先出。
写入队列空间不可用时会阻塞。
获取队列数据时当队列为空时将阻塞。
实现队列的方式多种,总的来说就是数组和链表;其实我们只需要搞清楚其中一个即可,不同的特性主要表现为数组和链表的区别。
这里的 ArrayBlockingQueue 看名字很明显是由数组实现。
我们先根据它这三个特性尝试自己实现试试。
初始化队列
我这里自定义了一个类:ArrayBlockQueue,它的构造函数如下:
//队列参数
public int size;
private volatile Object[] items;
private volatile int pollPoint = 0;
private volatile int addPoint = 0;
private volatile int count = 0;
//初始化队列容量
public ArrayBlockQueue(int size) {
this.size = size;
this.items = new Object[size];
}
写入操作
有几个需要注意的点:
队列满的时候,写入的线程需要被阻塞。
写入过队列的数量大于队列大小时需要从第一个下标开始写。
先看第一个队列满的时候,写入的线程需要被阻塞,先来考虑下如何才能使一个线程被阻塞,看起来的表象线程卡住啥事也做不了。
其实这样的一个特点很容易让我们想到 Java 的等待通知机制来实现线程间通信。
所以我这里的做法是,一旦队列满时就将写入线程调用 addLock.wait() 进入 waiting 状态,直到空间可用时再进行唤醒。
初始化 两个锁
//初始化两个锁
private Object addLock = new Object();
private Object pollLock = new Object();
写入操作代码
public void add(Object item) {
synchronized (addLock) {
//队列满则阻塞
while (count >= size) {
try {
addLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
items[addPoint] = item;
addPoint = (addPoint + 1) % size;
count++;
//当队列从0个元素添加到1个元素,则说明从空状态转非空状态可以通知一次取元素线程
if (count == 1)
synchronized (pollLock) {
pollLock.notifyAll();
}
}
}
所以这里声明了两个对象用于队列满、空情况下的互相通知作用。
在写入数据成功后需要使用 pollLock.notifyAll(),这样的目的是当获取队列为空时,一旦写入数据成功就可以把消费队列的线程唤醒。
这里的 wait 和 notify 操作都需要对各自的对象使用 synchronized 方法块,这是因为 wait 和 notifyAll 都需要获取到各自的锁。
消费队列
上文也提到了:当队列为空时,获取队列的线程需要被阻塞,直到队列中有数据时才被唤醒。
代码和写入的非常类似,也很好理解;只是这里的等待、唤醒恰好是相反的。
总的来说就是:
写入队列满时会阻塞直到获取线程消费了队列数据后唤醒写入线程。
消费队列空时会阻塞直到写入线程写入了队列数据后唤醒消费线程。
public Object poll() {
synchronized (pollLock) {
//队列空则阻塞
while (count == 0) {
try {
pollLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object value = items[pollPoint];
pollPoint = (pollPoint + 1) % size;
count--;
//当队列从满到非满,可以通知一次增添元素的线程
if (count == (size - 1))
synchronized (addLock) {
addLock.notifyAll();
}
return value;
}
}
测试
每一秒出队1个
//消费者
//每一秒出队1个
class Counsum extends Thread {
private ArrayBlockQueue queue;
public Counsum(ArrayBlockQueue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 出队一个数据: " + queue.poll());
}
}
}
每5秒入队2个,可见生产明显慢于消费
//生产者
//每5秒入队2个
class Product extends Thread {
private ArrayBlockQueue queue;
public Product(ArrayBlockQueue queue) {
this.queue = queue;
}
@Override
public void run() {
int i = 0;
while (true) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 0; j < 2; j++, i++) {
queue.add(i);
System.out.println(Thread.currentThread().getName() + "【入队一个数据: " + i+"】");
}
}
}
}
main
public class Test extends Object {
public static void main(String[] args) throws CloneNotSupportedException {
ArrayBlockQueue queue = new ArrayBlockQueue(5);
Thread counsum = new Counsum(queue);
Thread counsum1 = new Counsum(queue);
Thread counsum2 = new Counsum(queue);
Thread product = new Product(queue);
Thread product1 = new Product(queue);
Thread product2 = new Product(queue);
counsum.start();
product.start();
counsum1.start();
product1.start();
counsum2.start();
product2.start();
}
}
//output
消费者线程-1出队一个数据: 0
生成者线程-3入队一个数据: 0
消费者线程-3出队一个数据: 0
消费者线程-2出队一个数据: 0
生成者线程-2入队一个数据: 0
生成者线程-1入队一个数据: 0
生成者线程-2入队一个数据: 1
生成者线程-3入队一个数据: 1
生成者线程-1入队一个数据: 1
消费者线程-3出队一个数据: 1
消费者线程-1出队一个数据: 1
消费者线程-2出队一个数据: 1
消费者线程-2出队一个数据: 2
生成者线程-2入队一个数据: 2
消费者线程-1出队一个数据: 2
生成者线程-3入队一个数据: 2
生成者线程-1入队一个数据: 2
消费者线程-3出队一个数据: 2
生成者线程-1入队一个数据: 3
生成者线程-3入队一个数据: 3
生成者线程-2入队一个数据: 3
消费者线程-1出队一个数据: 3
消费者线程-3出队一个数据: 3
消费者线程-2出队一个数据: 3
使用lock锁版本
package concurrent;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ArrayBlockQueue {
//队列参数
public int size;
private volatile Object[] items;
private volatile int pollPoint = 0;
private volatile int addPoint = 0;
private volatile int count = 0;
//初始化两个锁
private ReentrantLock addLock = new ReentrantLock();
private ReentrantLock pollLock = new ReentrantLock();
//初始化两个信号量
private Condition full =addLock.newCondition();
private Condition empty =pollLock.newCondition();
//初始化队列容量
public ArrayBlockQueue(int size) {
this.size = size;
this.items = new Object[size];
}
public void add(Object item) {
addLock.lock();
{
//队列满则阻塞
while (count >= size) {
try {
full.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
items[addPoint] = item;
addPoint = (addPoint + 1) % size;
count++;
//当队列从0个元素添加到1个元素,则说明从空状态转非空状态可以通知一次取元素线程
if (count == 1) {
pollLock.lock();
empty.signal();
pollLock.unlock();
}
}
addLock.unlock();
}
public Object poll() {
Object value;
pollLock.lock();
{
//队列空则阻塞
while (count == 0) {
try {
empty.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
value = items[pollPoint];
pollPoint = (pollPoint + 1) % size;
count--;
//当队列从满到非满,可以通知一次增添元素的线程
if (count == (size - 1)) {
addLock.lock();
full.signal();
addLock.unlock();
}
}
pollLock.unlock();
return value;
}
}