【Java并发编程篇】阻塞队列ArrayBlockingQueue与LinkedBlockingQueue

什么是阻塞队列

阻塞队列最大的特性在于支持阻塞添加和阻塞删除方法:

  • 阻塞添加:当阻塞队列已满时,队列会阻塞加入元素的线程,直到队列元素不满时才重新唤醒线程执行加入元素操作。
  • 阻塞删除:但阻塞队列元素为空时,删除队列元素的线程将被阻塞,直到队列不为空再执行删除操作

Java 中的阻塞队列接口 BlockingQueue 继承自 Queue 接口,因此先来看看阻塞队列接口为我们提供的主要方法:

public interface BlockingQueue<E> extends Queue<E> {

    // 将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量)
    // 在成功时返回 true,如果此队列已满,则抛IllegalStateException。 
    boolean add(E e); 

    // 将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量) 
    // 如果该队列已满,则在到达指定的等待时间之前等待可用的空间,该方法可中断 
    boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException; 

    //将指定的元素插入此队列的尾部,如果该队列已满,则一直等到(阻塞)。 
    void put(E e) throws InterruptedException; 

    //获取并移除此队列的头部,如果没有元素则等待(阻塞),直到有元素将唤醒等待线程执行该操作 
    E take() throws InterruptedException; 

    //获取并移除此队列的头部,在指定的等待时间前一直等到获取元素, //超过时间方法将结束
    E poll(long timeout, TimeUnit unit) throws InterruptedException; 

    //从此队列中移除指定元素的单个实例(如果存在)。 
    boolean remove(Object o); 
}

//除了上述方法还有继承自Queue接口的方法 
//获取但不移除此队列的头元素,没有则跑异常NoSuchElementException 
E element(); 

//获取但不移除此队列的头;如果此队列为空,则返回 null。 
E peek(); 

//获取并移除此队列的头,如果此队列为空,则返回 null。 
E poll();

这里我们把上述操作进行分类:

(1)插入方法

  • add(E e):添加成功返回 true,失败抛 IllegalStateException 异常
  • offer(E e):成功返回 true,如果此队列已满,则返回 false
  • put(E e):将元素插入此队列的尾部,如果该队列已满,则一直阻塞

(2)删除方法

  • remove(Object o):移除指定元素,成功返回 true,失败返回 false
  • poll():获取并移除此队列的头元素,若队列为空,则返回 null
  • take():获取并移除此队列头元素,若没有元素则一直阻塞

(3)检查方法

  • element() :获取但不移除此队列的头元素,没有元素则抛异常
  • peek() :获取但不移除此队列的头;若队列为空,则返回 null

ArrayBlockingQueue 和 LinkedBlockingQueue的区别

(1)队列大小有所不同,ArrayBlockingQueue 是有界的初始化必须指定大小,而LinkedBlockingQueue 可以是有界的也可以是无界的(默认是 Integer.MAX_VALUE),对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题
(2)数据存储容器不同,ArrayBlockingQueue 采用的是数组作为数据存储容器,而LinkedBlockingQueue 采用的则是以 Node 节点作为连接对象的链表
(3)创建与销毁对象的开销不同,ArrayBlockingQueue 采用数组作为存储容器,在插入或删除元素时不会产生或销毁任何额外的对象实例,而 LinkedBlockingQueue 则会生成一个额外的 Node 对象。在长时间内需要高效并发地处理大批量数据的时,对于GC可能存在较大影响。
(4)队列添加或移除的锁不一样,ArrayBlockingQueue 的锁是没有分离的,添加操作和移除操作采用同一个 ReenterLock 锁,而 LinkedBlockingQueue 的锁是分离的,添加采用的是 putLock,移除采用的是 takeLock,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

 

参考:

 

posted @ 2021-12-22 22:59  残城碎梦  阅读(56)  评论(0编辑  收藏  举报