1.介绍
ArrayBlockingQueue是一个阻塞式的队列,在看jdk内部尤其是一些多线程,大量使用了blockinkQueue 来做的。blockinkQueue 继承自AbstractBlockingQueue,间接的实现了Queue接口和Collection接口。底层以数组的形式保存数据(实际上可看作一个循环数组)。
ArrayBlockingQueue有哪些缺点呢?
a)队列长度固定且必须在初始化时指定,所以使用之前一定要慎重考虑好容量;
b)如果消费速度跟不上入队速度,则会导致提供者线程一直阻塞,且越阻塞越多,非常危险;
c)只使用了一个锁来控制入队出队,效率较低,那是不是可以借助分段的思想把入队出队分裂成两个锁呢?
常用的操作包括 add ,offer,put,remove,poll,take,peek。前三者add offer put 是插入的操作。后面四个方法是取出的操作。他们之间的区别和关联:
add: 内部实际上获取的offer方法,当Queue已经满了时,抛出一个异常。不会阻塞。
offer:当Queue已经满了时,返回false。不会阻塞。
put:当Queue已经满了时,会进入等待,只要不被中断,就会插入数据到队列中。会阻塞,可以响应中断。
取出方法中 remove和add相互对应。也就是说,调用remove方法时,假如队列为空,则抛出异常。另外的,poll与offer相互对应。take和put相互对应。peek方法比较特殊,前三个取出的方法,都会将元素从Queue的头部溢出,但是peek不会,实际上只是,获取队列头的元素。peek方法也不会阻塞。当队列为空时,直接返回Null。
2.对比LinkedBlockingQueue
LinkedBlockingQueue也是一个阻塞式的队列,与ArrayBlockingQueue的区别是什么呢?
LinkedBlockingQueue保存元素的是一个链表。其内部有一个Node的内部类,其中有一个成员变量 Node next。就这样形成了一个链表的结构,要获取下一个元素,只要调用next就可以了。而ArrayBlockingQueue则是一个数组。
LinkedBlockingQueue内部读写(插入获取)各有一个锁,而ArrayBlockingQueue则读写共享一个锁。
3.选择LinkedBlockingQueue还是ArrayBlockingQueue
个人感觉大多数场景适合使用LinkedBlockingQueue。在JDK源码当中有说明,LinkedBlockingQueue比ArrayBlockingQueue有更高的吞吐量,但是性能表现更难预测(也就是说相比ArrayBlockingQueue性能表现不稳定,但是也很稳定了)。
为什么会有吞吐量的区别,个人以为可能是ArrayBlockingQueue两个锁的缘故,在大量并发的情况下,插入和读取都很多时,就会造成一点的时间浪费。
还有一点,应为LinkedBlockingQueue创建时,默认会直接创建一个Integer.MAX_VALUE的数组,当插入少,读取多时,就会造成很大的空间浪费。而LinkedBlockingQueue实际上实在等需要的时候才会创建一个Node节点。
4.1 保存数据的结构,是一个Object的数组
/** The queued items */
final Object[] items;
4.2 下一次取元素的索引位置 poll, peek or remove
下一次入队的位置 putIndex put, offer, or add
/** items index for next take, poll, peek or remove */ int takeIndex;/** items index for next put, offer, or add */ int putIndex;/** Number of elements in the queue */ 队列中元素数量 int count;4.2全局锁,这是一个掌管所有访问操作的锁。读写都会使用这个锁
/** Main lock guarding all access */
final ReentrantLock lock;/** Condition for waiting takes */ 出列条件 private final Condition notEmpty; /** Condition for waiting puts */ 入列条件 private final Condition notFull;
构造器源码
初始化指定容量的数组,初始化非公平的重入锁,初始化读等待队列,初始化写等待队列。
public ArrayBlockingQueue(int capacity) {
this(capacity, 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();
notFull = lock.newCondition();
}
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
// 初始化构造器
this(capacity, fair);
// 获取重入锁
final ReentrantLock lock = this.lock;
// 将当前线程锁定
lock.lock(); // Lock only for visibility, not mutual exclusion
try {
int i = 0;
try {
// 遍历集合
for (E e : c) {
// 检查元素是否为空,为空抛出空指针异常
checkNotNull(e);
// 存放元素
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
// 初始化数组中元素的个数为计算后的i
count = i;
// 插入元素的下标索引,如果i的容量达到了指定的容量
// 插入元素的下标为0,否则为i
putIndex = (i == capacity) ? 0 : i;
} finally {
// 释放当前线程的锁
lock.unlock();
}
}
入队方法(都需要等待获取锁 - > 当获取到锁之后)
ArrayBlockingQueue提供了诸多方法,可以将元素加入队列尾部。
- add(E e) :将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则抛出 IllegalStateException
- offer(E e) :将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false
- offer(E e, long timeout, TimeUnit unit) :将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间
- put(E e) :将指定的元素插入此队列的尾部,如果该队列已满,则堵塞等待可用的空间, 直到出队dequeue()方法唤醒
public boolean add(E e) {
return super.add(e);
}
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock(); // 一直等到获取锁
try {
if (count == items.length) //假如当前容纳的元素个数已经等于数组长度,那么返回false
return false;
else {
enqueue(e); // 将元素插入到队列中,返回true
return true;
}
} finally {
lock.unlock(); //释放锁
}
}
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
// 加锁
lock.lockInterruptibly();
try {
// 如果数组满了,就阻塞nanos纳秒
// 如果唤醒这个线程时依然没有空间且时间到了就返回false
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
// 入队
enqueue(e);
return true;
} finally {
// 解锁
lock.unlock();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
//putIndex是下一个放至元素的坐标,添加元素到数组中
items[putIndex] = x;
if (++putIndex == items.length) //putIndex+1, 并且比较是否与数组长度相同,是的话,则从数组开头
putIndex = 0; //插入元素,这就是循环数组的奥秘了
count++; //元素总量+1
notEmpty.signal();// 唤醒阻塞出列的线程 (如果队列为空,则进行出列操作是会阻塞)
}
put: 将指定的元素插入此队列的尾部,如果该队列已满,会堵塞等待可用的空间 ,只要不被中断,就会插入数据到队列中
private final Condition notFull;
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
//如果当前的线程没有发生中断,那么就将当前线程锁定, 如果线程中断了抛出异常
lock.lockInterruptibly();
try {
//队列满了,一直阻塞在这里
while (count == items.length)
notFull.await();
//元素入队
enqueue(e);
} finally {
lock.unlock();
}
}
出队方法
- poll() :获取并移除此队列的头,如果此队列为空,则返回 null
- poll(long timeout, TimeUnit unit) :获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)
- remove(Object o) :从此队列中移除指定元素的单个实例(如果存在)
- take() :获取并移除此队列的头部,在元素变得可用之前一直堵塞等待(如果有必要),直到入队方法enqueue() 唤醒
- peek():不会真正的从队列中删除元素,实际上只是取出头元素而已。
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//假如当前队列中的元素为空,返回null,否则返回出列的元素
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
// 加锁
lock.lockInterruptibly();
try {
// 如果队列无元素,则阻塞等待nanos纳秒
// 如果下一次这个线程获得了锁但队列依然无元素且已超时就返回null
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
//元素出队
private E dequeue() {
final Object[] items = this.items;
E x = (E) items[takeIndex];
//将要取出的元素指向null 表示这个元素已经取出去了
items[takeIndex] = null;
//takeIndex +1,同样的假如已经取到了数组的末尾,那么就要重头开始取
if (++takeIndex == items.length)
takeIndex = 0; //这就是循环数组
count--;
if (itrs != null)
itrs.elementDequeued(); // 如果迭代器itrs不为null,则需要维护下该迭代器
//唤醒堵塞的入列线程 (和notFull.await()对应)
notFull.signal();
return x;
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
//如果当前的线程没有发生中断,那么就将当前线程锁定,如果线程中断了抛出异常
lock.lockInterruptibly();
try {
// /如果数组中的元素数量为0,那么取元素的线程阻塞在此处
while (count == 0)
// 将当前线程进行等待(和notEmpty.signal()方法对应)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
peek 方法不会真正的从队列中删除元素,实际上只是取出头元素而已。
/** items index for next take, poll, peek or remove */
int takeIndex;
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
remove
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
if (o.equals(items[i])) {
removeAt(i);
return true;
}
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
//count=0 直接返回false
return false;
} finally {
lock.unlock();
}
}
http://cmsblogs.com/?p=2381 http://cmsblogs.com/?p=4755