1、BlockingQueue使用
介绍:BlockingQueue即阻塞队列,常用的是用于实现生产者与消费者模式,它算是一种使用ReentrantLock特性的一个典型的案例
先上代码介绍下BlockingQueue的基本使用和场景
public class TestQueue {
static BlockingQueue<String> blockingQueue = new ArrayBlockingQueue(10);
{
init();
}
public void init() {
new Thread(()->{
while(true) {
try {
System.out.println(blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
public static void main(String[] args) {
TestQueue init = new TestQueue();
new Thread(()->{
while(true) {
try {
blockingQueue.put("jack");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(()->{
while(true) {
try {
blockingQueue.put("jack2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
如上main线程初始化TestQueue类调用了init方法创建了一个消费者线程阻塞在blockingQueue.take()方法处,创建生产者线程往阻塞队列放数据即可被消费者线程消费,上述代码即是一个简单的生产者消费者队列
2、BlockingQueue原理分析
我们先看下ArrayBlockingQueue的构造方法
public ArrayBlockingQueue(int var1, boolean var2) {
this.itrs = null;
if (var1 <= 0) {
throw new IllegalArgumentException();
} else {
// 创建了一个内部数组,大小为阻塞队列的大小值
this.items = new Object[var1];
// 创建了一个重入锁
this.lock = new ReentrantLock(var2);
// 根据锁创建了两个Condition用于后续条件判断
this.notEmpty = this.lock.newCondition();
this.notFull = this.lock.newCondition();
}
}
然后我们看下put方法
public void put(E var1) throws InterruptedException {
checkNotNull(var1);
ReentrantLock var2 = this.lock;
// 拿到锁加锁操作,并且做了一些额外的事
var2.lockInterruptibly();
try {
while(this.count == this.items.length) {
// 当前长度等于数组最大长度时,
// 阻塞并挂起当前线程,等待消费者唤醒
this.notFull.await();
}
// 加入到数组
this.enqueue(var1);
} finally {
var2.unlock();
}
}
private void enqueue(E var1) {
Object[] var2 = this.items;
// 获取到当前数组,并且拿到当前put的数组下标,设置当前值
var2[this.putIndex] = var1;
if (++this.putIndex == var2.length) {
// 如果当前值数组满了,那么设置putIndex 为0,即下一次put会从0开始
// (下次put时满了必定会阻塞,直到消费者把0位置的元素消费掉了,
// putIndex的设计即保证了按下标0开始一直循环,而且每次设置值是当前下标的值
// 必定是已经被消费掉的空位置)
this.putIndex = 0;
}
++this.count;
// 唤醒因为队列为空阻塞在notEmpty的一个消费者
this.notEmpty.signal();
}
再看下take()方法
public E take() throws InterruptedException {
// 大致和put方法类似
ReentrantLock var1 = this.lock;
var1.lockInterruptibly();
Object var2;
try {
while(this.count == 0) {
// 当目前数组为0时condition阻塞当前消费者等待被唤醒
this.notEmpty.await();
}
var2 = this.dequeue();
} finally {
var1.unlock();
}
return var2;
}
private E dequeue() {
Object[] var1 = this.items;
// takeIndex即消费者的下标,也是从0开始,即新进先出原则,设计和putIndex类似,
// 依次循环,并且可以保证每次拿到的takeIndex下标的对象都是有值的
// (没有值会阻塞在上面的this.notEmpty.await();)
Object var2 = var1[this.takeIndex];
var1[this.takeIndex] = null;
if (++this.takeIndex == var1.length) {
this.takeIndex = 0;
}
--this.count;
if (this.itrs != null) {
this.itrs.elementDequeued();
}
// 当消费了一个元素后,唤醒当数组满了之后阻塞的其中一个生产者
this.notFull.signal();
return var2;
}
总结:ArrayBlockingQueue主要就是往数组加元素和取元素来实现功能,其中的有界队列使用了condition的await()阻塞和signal()唤醒来实现线程的等待(condition的功能类似于Object的wait/notify,主要用于等待和唤醒使用)