BlockingQueue接口
BlockingQueue接口继承自Queue接口:
public interface BlockingQueue<E>extends Queue<E>
与Queue队列相比,它是线程安全的。添加和移除元素有四类方式,其中add()、remove()、offer()、poll()、element()、peek()方法继承自Queue。
Throws exception | Special value | Blocks | Times out | |
Insert | boolean add(e) | boolean offer(e) | void put(e) | boolean offer(e, time, unit) |
Remove | E remove() | E poll() | E take() | E poll(time, unit) |
Examine | E element() | E peek() | not applicable | not applicable |
通俗的说:
- 不能搞时抛出异常;
- 不能搞时返回null或false
- 不能搞时一直等,直到能搞且搞到为止
- 一段时间内尝试去搞,实在搞不了返回null或false
除此之外,还有以下方法:
int remainingCapacity(); 队列剩余存储空间
boolean remove(Object o); 删除队列中的特定元素
public boolean contains(Object o); 队列中是否包含特定元素
int drainTo(Collection<? super E> c); 把队列中的元素删除并添加到指定的集合中
int drainTo(Collection<? super E> c, int maxElements); 把队列中的maxElements个元素删除添加到指定集合
ArrayBlockingQueue
底层以数组来存储元素,且构造函数必须至少指定队列大小,只使用一个可重入锁来来控制线程访问:
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/** 底层存储元素数组 */
private final E[] items;
private int takeIndex;
private int putIndex;
private int count;
// 使用经典的two-condition算法进行并发控制
private final ReentrantLock lock;
private final Condition notEmpty;
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 = (E[]) 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);
if (capacity < c.size())
throw new IllegalArgumentException();
for (Iterator<? extends E> it = c.iterator(); it.hasNext();)
add(it.next());
}
...
}
put()和take()方法是经典的生产者-消费者算法:
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
final E[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
try {
while (count == items.length)
notFull.await(); //使用while自旋锁
} catch (InterruptedException ie) {
notFull.signal(); // propagate to non-interrupted thread
throw ie;
}
insert(e); //插入元素,释放notEmpty信号
} finally {
lock.unlock();
}
}
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal();
}
take()方法如下:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
try {
while (count == 0)
notEmpty.await();
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to non-interrupted thread
throw ie;
}
E x = extract();
return x;
} finally {
lock.unlock();
}
}
private E extract() {
final E[] items = this.items;
E x = items[takeIndex];
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal();
return x;
}
LinkedBlockingQueue
LinkedBlockingQueue与ArrayBlockingQueue相比有以下不同:
- 底层使用链表而非数组存储元素;
- 使用两个锁来控制线程访问,这样队列可以同时进行put和take的操作,因此吞吐量相对ArrayBlockingQueue就高。
- 可以不指定队列大小,此时默认大小为Integer.MAX_VALUE
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/** 队列边界大小,不指定为默认为Integer.MAX_VALUE */
private final int capacity;
private final AtomicInteger count = new AtomicInteger(0);
private transient Node<E> head;
private transient Node<E> last;
/** 控制take、poll的锁 */
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();
/** 控制put、offer的锁 */
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
final ReentrantLock putLock = this.putLock;
putLock.lock(); // Never contended, but necessary for visibility
try {
int n = 0;
for (E e : c) {
if (e == null)
throw new NullPointerException();
if (n == capacity)
throw new IllegalStateException("Queue full");
enqueue(e);
++n;
}
count.set(n);
} finally {
putLock.unlock();
}
}
}
看下put操作源码:
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
// 当队列大小等于队列边界大小时,使用自旋锁等待
while (count.get() == capacity) {
notFull.await();
}
enqueue(e); // 队列最后插入元素
c = count.getAndIncrement();
// 队列不满,则发送notFull信号量,其他线程可以进行put
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
//队列为空,发送notEmpty信号量,通知线程可以进行take
signalNotEmpty();
}
private void enqueue(E x) {
// assert putLock.isHeldByCurrentThread();
last = last.next = new Node<E>(x);
}
take()方法与put类似:
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
使用Executors.newFixedThreadPool()方法,使用的就是LinkedBlockingQueue存储的等待线程队列。