并发编程之阻塞队列
队列实质就是一种存储数据的结构 通常用链表或者数组实现 一般而言队列具备FIFO先进先出的特性,当然也有双端队列(Deque)优先级队列 主要操作:入队(EnQueue)与出队(Dequeue)
BlockingQueue
队列实质就是一种存储数据的结构 通常用链表或者数组实现 一般而言队列具备FIFO先进先出的特性,当然也有双端队列(Deque)优先级队列 主要操作:入队(EnQueue)与出队(Dequeue)
1、ArrayBlockingQueue 由数组支持的有界队列
2、LinkedBlockingQueue 由链接节点支持的可选有界队列
3、PriorityBlockingQueue 由优先级堆支持的无界优先级队列
4、DelayQueue 由优先级堆支持的、基于时间的调度队列
BlockingQueue底层依赖ReenTrantLock 和Condition(条件队列只能在独占模式下使用,共享模式不能用)(AQS框架实现)
ArrayBlockingQueue
BlockingQueue<Ball> blockingQueue = new ArrayBlockingQueue<Ball>(1);初始化源码分析
public ArrayBlockingQueue(int capacity) { this(capacity, false); -------------false默认创建时非公平锁 }
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];-------初始化数组
lock = new ReentrantLock(fair);------创建非公平锁
notEmpty = lock.newCondition(); ---------------初始化了两个条件队列,对应两个条件对列notEmpty ,notFull
notFull = lock.newCondition();
}
// 两个条件队列如下图:
为什么会有连两个条件队列,因为有put(生产者),和take(消费者) 两个操作 put的时候要看(Notfull)是不是满了,take(Notempty)是看是不是空了。
put生产者操作源码解读
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); //----先上来获取独占锁,才能进入下面的逻辑。
try {
while (count == items.length)
notFull.await(); // 判断当前队列是不是已经满了,如果count == items.length,那么这个下线程就要入条件队列等待
enqueue(e);
} finally {
lock.unlock();
}
}
/**
* 加入条件队列等待,条件队列入口
*/
public final void await() throws InterruptedException {
//如果当前线程被中断则直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//把当前节点加入条件队列
Node node = addConditionWaiter();
//释放掉已经获取的独占锁资源
int savedState = fullyRelease(node);
int interruptMode = 0;
//如果不在同步队列中则不断挂起
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
//这里被唤醒可能是正常的signal操作也可能是中断
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
/**
* 走到这里说明节点已经条件满足被加入到了同步队列中或者中断了
* 这个方法很熟悉吧?就跟独占锁调用同样的获取锁方法,从这里可以看出条件队列只能用于独占锁
* 在处理中断之前首先要做的是从同步队列中成功获取锁资源
*/
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//走到这里说明已经成功获取到了独占锁,接下来就做些收尾工作
//删除条件队列中被取消的节点
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
//根据不同模式处理中断
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
/**
* 这里的判断逻辑是:
* 1.如果现在不是中断的,即正常被signal唤醒则返回0
* 2.如果节点由中断加入同步队列则返回THROW_IE,由signal加入同步队列则返回REINTERRUPT
*/
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
// 这个方法是把条件节点转运到CLH同步队列中并把信号量置为初始值0,返回转运成功与否标志。只有把条件节点转运到同步队列才又可能被唤醒。
final boolean transferAfterCancelledWait(Node node) {
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
/*
* If we lost out to a signal(), then we can"t proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
*/
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}