Java在jdk1.6引入了并发容器阻塞队列,阻塞队列是一个统称,凡是实现了BlockingQueue接口的容器都可以称的上阻塞队列,它实现了生产者-消费者模式,在阻塞队列中出队列与入队列都有两种形式,阻塞的put与take方法以及定时获取的offer与poll方法,BlockingQueue接口源码如下:
public interface BlockingQueue<E> extends Queue<E> {
boolean add(E e);
boolean offer(E e);
void put(E e) throws InterruptedException;
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
E take() throws InterruptedException;
E poll(long timeout, TimeUnit unit)
int remainingCapacity();
boolean remove(Object o);
public boolean contains(Object o);
//......
}
J.U.C中的并发容器用来替代Collections类中创建的同步容器,相较于同步队列,并发容器有着更好的锁粒度以及更好的在多线程环境下的并发策略,所以并发容器的并发性能都会比同步容器要好很多。
ArrayBlockingQueue类是阻塞队列的Array实现,它是有界的,它在并发情况下的策略是持有一个ReetrantLock锁,并用Condition持有两个等待队列来实现阻塞,一个负责管理空的情况,一个负责管理满的情况:
final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;
之所以称它是一个有界的队列,是因为它的大小在创建时已经固定,不能改变了:
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 void put(E e) throws InterruptedException {
//检查值部位空
checkNotNull(e);
final ReentrantLock lock = this.lock;
//获取全局锁
lock.lockInterruptibly();
try {
//当队列已满,调用await方法陷入等待
while (count == items.length)
notFull.await();
//没有满,则入队列
enqueue(e);
} finally {
lock.unlock();
}
}
从源码中看出,当队列中已满的情况下,会调用await方法让当前线程陷入等待,并释放锁,只有其它线程取出了队列中的元素,并唤醒因队列满情况而陷入等待的线程,才会让元素入队列。阻塞的take出队方法与入队刚好相反,源码如下:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
当队列为空的时候会阻塞当前线程,dequeue与enqueue方法里会调用signal方法唤醒对应的阻塞线程。还提供了一组入队以及出队的方法来灵活的处理入队以及出队:
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 E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
当队列为空或者已满的情况下,都会立马返回,而不会陷入等待,同时这两个方法还有等待时间的重载方法,当等待一定时间后,如果没有得到想要的结果,那么就会返回失败的结果。
LinkedBlockingQueue是阻塞队列的链表实现,它在内部持有两个锁,一个入队锁,一个出队锁,并使用原子变量来记录队列的长度,它也是个有界队列:
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
虽然是用链表实现的,但是在创建LinkedBlockingQueue类时,必须规定队列的大小,而且不能更改:
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
入队以及出队的原理与ArrayBlockingQueue类原理是一样的,但是由于入队锁以及出队锁的分离,可以实现入队和出队的同时执进行,这样就提高了性能效率。因为出队以及入队的操作并不是在同一个地方,入队在队尾操作,出队在队头进行,所以两者是影响不大的。所以一般推荐使用LinkedBlockingQueue类,因为并发效率高。
PriorityBlockingQueue类是优先级队列,如果你不想按照先进先去的方式排列,你可以使用这个类,当然进入该队列的要实现Comparable接口或者Comparator接口。
LinkedBlockingDeque类则是双端LinkedBlockingQueue类的实现,可以在两头入队和出队操作,实现的原理是一样的。
CopyOnWriteArrayList类与CopyOnWriteArraySet类保持同步的操作是,发布一个不变的对象,意思是说,如果有插入的操作,那么利用锁来同步重新构建一个内部数组,只要内部数组不变那么在并发的情况下访问到的数据就是正确的。但是缺点就是每次改变数据都要在内部构建一个新的数组,降低了效率,所以这两个容器适用于读操作特别多,但是写入操作很少的场景。
ConcurrentLinkedQueue与ConcurrentLinkedDeque类内部采用CAS原子操作来保证原子性。