ArrayBlockingQueue源码分析
ArrayBlockingQueue底层是使用一个数组实现队列的,并且在构造ArrayBlockingQueue时需要指定容量,也就意味着底层数组一旦创建了,容量就不能改变了,因此ArrayBlockingQueue是一个容量限制的阻塞队列。因此,在队列全满时执行入队将会阻塞,在队列为空时出队同样将会阻塞。
ArrayBlockingQueue的重要字段有如下几个:
/** The queued items */ final Object[] items; /** Main lock guarding all access */ final ReentrantLock lock; /** Condition for waiting takes */ private final Condition notEmpty; /** Condition for waiting puts */ private final Condition notFull;
一、put(E e)方法
put(E e)方法在队列不满的情况下,将会将元素添加到队列尾部,如果队列已满,将会阻塞,直到队列中有剩余空间可以插入。该方法的实现如下:
/** * Inserts the specified element at the tail of this queue, waiting * for space to become available if the queue is full. * * @throws InterruptedException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public void put(E e) throws InterruptedException { Objects.requireNonNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) notFull.await(); enqueue(e); } finally { lock.unlock(); } }
put方法总结:
- 1. ArrayBlockingQueue不允许元素为null
- 2. ArrayBlockingQueue在队列已满时将会调用notFull的await()方法释放锁并处于阻塞状态。
- 3. 一旦ArrayBlockingQueue不为满的状态,就将元素入队。
二、E take()方法
take()方法用于取走队头的元素,当队列为空时将会阻塞,直到队列中有元素可取走时将会被释放。其实现如下:
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await(); return dequeue(); } finally { lock.unlock(); } }
take方法总结:
一旦获得了锁之后,如果队列为空,那么将阻塞;否则调用dequeue()出队一个元素。
ArrayBlockingQueue总结:
ArrayBlockingQueue的并发阻塞是通过ReentrantLock和Condition来实现的,ArrayBlockingQueue内部只有一把锁,意味着同一时刻只有一个线程能进行入队或者出队的操作。
三、总结
在上一篇分析LinkedBlockingQueue的源码之后,可以与ArrayBlockingQueue做一个比较。
1、ArrayBlockingQueue:
一个对象数组+一把锁+两个条件。入队与出队都用同一把锁,在只有入队高并发或出队高并发的情况下,因为操作数组,且不需要扩容,性能很高,采用了数组,必须指定大小,即容量有限。
2、LinkedBlockingQueue:
一个单向链表+两把锁+两个条件。两把锁,一把用于入队,一把用于出队,有效的避免了入队与出队时使用一把锁带来的竞争。在入队与出队都高并发的情况下,性能比ArrayBlockingQueue高很多。