一、BlockingQueue
1、BlockingQueue不接受null元素,如果写入null,会抛出NullPointException
2、它被设计用来做生产-消费队列
3、由于它间接继承了Collection接口,所以,可以通过remove(x)来移除队列中任意一个元素,但是,除了处理对头和队尾的元素,操作其他地方的元素会影响性能,所以,偶尔使用remove方法是可以的,不建议频繁使用。
4、BlockingQueue是线程安全的
5、有些实现类的容量是受限的,所以,插入元素要注意。
示例
典型的生产-消费模式,在多个生产者和消费者下能保证线程安全。
class Producer implements Runnable {
private final BlockingQueue queue;
Producer(BlockingQueue q) { queue = q; }
public void run() {
try {
while (true) { queue.put(produce()); }
} catch (InterruptedException ex) { ... handle ...}
}
Object produce() { ... }
}
class Consumer implements Runnable {
private final BlockingQueue queue;
Consumer(BlockingQueue q) { queue = q; }
public void run() {
try {
while (true) { consume(queue.take()); }
} catch (InterruptedException ex) { ... handle ...}
}
void consume(Object x) { ... }
}
class Setup {
void main() {
BlockingQueue q = new SomeQueueImplementation();
Producer p = new Producer(q);
Consumer c1 = new Consumer(q);
Consumer c2 = new Consumer(q);
new Thread(p).start();
new Thread(c1).start();
new Thread(c2).start();
}
源码分析
public interface BlockingQueue<E> extends Queue<E> {
//该方法是添加一个元素到队列中,如果队列不是满的,则返回true,如果队列满的,则抛出IllegalStateException异常
boolean add(E e);
//该方法时添加一个元素到队列中,如果队列不是满的,则返回true,如果队列满的,则返回false
boolean offer(E e);
//添加一个元素到队列中,如果队列是满的,则阻塞直到消费者消费了队列元素使得队列有空余空间
void put(E e) throws InterruptedException;
//和上面的put方法一样的逻辑
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
//从队列中获取一个元素,相当于消费,但是,如果队列中无元素可消费,则阻塞直到生产者生产了元素放入队列
E take() throws InterruptedException;
// 和上面的take逻辑一样
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
//返回剩余容量
int remainingCapacity();
//移除指定元素
boolean remove(Object o);
//是否包含指定元素
public boolean contains(Object o);
//移除队列中所有的元素,添加到指定的集合中去,这个操作比重复的poll这个队列要更高效
int drainTo(Collection<? super E> c);
//同上逻辑,不同的是,这里是移除队列中指定数量的元素到集合中去
int drainTo(Collection<? super E> c, int maxElements);
}
二、BlockingDeque
1、实现了BlockingQueue,所以有BlockingQueue接口的所有特性:不接受一个null值,是线程安全的,容量可固定或不限制。
2、BlockingDeque的实现类主要用于一个FIFO的BlockingQueue。
3、Deque是double ended queue,双端队列的简称。双端队列是一个你可以从任意一端插入或者抽取元素的队列。
4、BlockingDeque实现了BlockingQueue,里面几乎有BlockingQueue的所有方法和自己的一套方法,功能比BlockingQueue更加的灵活强大。
源码分析
public interface BlockingDeque<E> extends BlockingQueue<E>, Deque<E> {
//插入一个元素到双端队列的头,如果容量超过限制,则抛出IllegalStateException异常
void addFirst(E e);
//插入一个元素到双端队列的尾,如果容量超过限制,则抛出IllegalStateException异常
void addLast(E e);
//这和addFirst差不多,但是有返回值
boolean offerFirst(E e);
//这个和 addLast差不多,但是有返回值
boolean offerLast(E e);
//插入一个元素到双端队列的头,如果队列满了,则会一直等待直到队列中有空闲位置。
void putFirst(E e) throws InterruptedException;
//插入一个元素到双端队列的尾,如果队列满了,则会一直等待直到队列中有空闲位置。
void putLast(E e) throws InterruptedException;
//和putFirst一样,如果满了,会一直等待直到有空闲位置
boolean offerFirst(E e, long timeout, TimeUnit unit)
throws InterruptedException;
//和putLast一样,如果满了,会一直等待直到有空闲位置
boolean offerLast(E e, long timeout, TimeUnit unit)
throws InterruptedException;
//检索并移除双端队列中的第一个元素,如果该队列是空的,则一直等待直到有元素
E takeFirst() throws InterruptedException;
//和上面逻辑一样
E takeLast() throws InterruptedException;
//和offerFirst一样
E pollFirst(long timeout, TimeUnit unit)
throws InterruptedException;
//和offerLast一样
E pollLast(long timeout, TimeUnit unit)
throws InterruptedException;
//移除掉队列中遇到的第一个指定的元素,如果没有,则队列保持不变
boolean removeFirstOccurrence(Object o);
boolean removeLastOccurrence(Object o);
//和BlockingQueue中的一样
boolean add(E e);
//和BlockingQueue中的一样
boolean offer(E e);
//插入一个元素到队尾,不符合条件会一直等待
void put(E e) throws InterruptedException;
//插入一个元素到队尾,不符合条件会一直等待
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
//检索和移除队列中的头,这个方法和poll的区别在于:如果队列是空的,则抛出NoSuchElementException
E remove();
E poll();
//检索和移除队列中的头,也就是双端队列的第一个元素,条件不符合会一直等待
E take() throws InterruptedException;
//和上面一样
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
//检索但是不移除,检索队列的头,也就是双端队列的第一个元素,这个方法与peek的区别在于:如果队列为空,这个方法会抛出NoSuchElementException
E element();
E peek();
//移除掉第一个与指定元素相符的元素,如果队列中没有匹配的元素,则队列不变
boolean remove(Object o);
public boolean contains(Object o);
public int size();
Iterator<E> iterator();
//push一个元素到此双端队列表示的栈里,这个方法等价于addFirst(o)
void push(E e);
}
三、BlockingQueue和BlockingDeque的区别
1、BlockingQueue用于做消息队列,而BlockingDeque实现了BlockingQueue接口,用于做FIFO的消息队列。
2、如果各种插入方法将会把新元素添加到双端队列的尾端,而移除方法将会把双端队列的首端的元素移除。正如 BlockingQueue 接口的插入和移除方法一样。
四、数组阻塞队列 ArrayBlockingQueue
1、绑定了BlockingQueue通过数组。
2、这个队列保证元素的位置是FIFO的。队列中的头元素必须是整个队列中存在时间最长,队列中的尾节点必须是整个队列中存活时间最短的。添加元素只能在队尾,删除元素只能在对头。
3、固定大小的数组长度,一旦创建,容量不能改变。尝试从一个满的队列中put一个元素或尝试从一个空的队列中take一个元素都会阻塞。
4、这个队列对于等待的生产者和消费者是公平的策略。
5、该队列可实现公平和非公平的获取锁,在多线程的情况下。但默认是非公平获取锁。
主要源码分析
//可见这玩意实现了BlockingQueue接口
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
private static final long serialVersionUID = -817911632652898426L;
//用Object数组来当一个队列
final Object[] items;
//存放队列的头元素的下标
int takeIndex;
//存放队列的尾元素的下标
int putIndex;
//队列中元素的数量
int count;
//ArrayBlockingQueue就是用的可重入锁来实现线程的安全,保证FIFO公平性,但是,你也可以在构造这个对象时指定可重入锁为非公平的!
final ReentrantLock lock;
//对于take操作的线程的等待队列
private final Condition notEmpty;
//对于put操作的线程的等待队列
private final Condition notFull;
//这玩意就是iterators,不管这个东西,用于并发操作用的
transient Itrs itrs = null;
//循环的减i,不知道啥
final int dec(int i) {
return ((i == 0) ? items.length : i) - 1;
}
@SuppressWarnings("unchecked")
final E itemAt(int i) {
return (E) items[i];
}
private static void checkNotNull(Object v) {
if (v == null)
throw new NullPointerException();
}
=============================================================================================
关键方法
//入队
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++; //入队后count加一
notEmpty.signal(); //入队后通过signal信号唤醒在条件队列中等待的take操作的线程
}
//出队
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null; //将这个下标位置置为null,方便GC
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal(); //出队后通过signal信号唤醒在条件队列中等待的put操作的线程
return x;
}
===============================================================================================
void removeAt(final int removeIndex) {
final Object[] items = this.items;
if (removeIndex == takeIndex) {
items[takeIndex] = null; //方便GC
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
} else {
final int putIndex = this.putIndex;
for (int i = removeIndex;;) {
int next = i + 1;
if (next == items.length)
next = 0;
if (next != putIndex) {
items[i] = items[next];
i = next;
} else {
items[i] = null;
this.putIndex = i;
break;
}
}
count--;
if (itrs != null)
itrs.removedAt(removeIndex);
}
notFull.signal();
}
//这是ArrayBlockingQueue默认的构造方法,可见默认是非公平的,这样做的目的是为了提高效率,因为,保证公平会让线程按照FIFO的顺序执行,效率不高。
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();
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
public boolean add(E e) {
return super.add(e);
}
================================================================================================
方法都使用了可重入锁来保证线程安全,通过Lock和unlock来加锁和解锁
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await(); //如果队列是满的,则使用await方法将当前线程阻塞,并生成节点放入到条件队列中等待。
enqueue(e);
} 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 {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return 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 {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
public int size() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
public int remainingCapacity() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return items.length - count;
} finally {
lock.unlock();
}
}
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);
}
return false;
} finally {
lock.unlock();
}
}
public boolean contains(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]))
return true
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
总结:
1、ArrayBlockingQueue实现了BlockingQueue,通过object数组来充当队列。
2、能够实现FIFO,但是默认情况下不使用公平锁来保证FIFO,实现公平与否使用的是Reentrantlock可重入锁来保证的,调用lock和unlock来加锁和解锁。所以,追根溯源,公平与否使用的是可重入锁中的公平锁和非公平锁。
3、ArrayBlockingQueue由于是数组实现的队列,所以,容量在初始化时就固定了,不可再改。
4、使用了Condition条件队列和signal信号量来保证线程的通信。换句话说,就是条件队列中的线程通过signal信号可被唤醒重新获取CPU资源,条件不符合就调用await方法生成节点存入条件队列。
五、结束语
类似的还有链阻塞队列 LinkedBlockingQueue、优先级的阻塞队列 PriorityBlockingQueue、同步队列SynchronousQueue
源码千千万,通过对源码的分析,我们学习的是怎么学习源码,如何学习,而不是学会某个阻塞队列!