ArrayBlockingQueue 与 LinkedBlockingQueue

http://www.javashuo.com/article/p-nidzvcyi-p.htmljava

  • ArrayBlockingQueue内部的阻塞队列是经过重入锁ReenterLockCondition条件队列实现的,
    • 因此ArrayBlockingQueue中的元素存在公平访问与非公平访问的区别,
    • 对于公平访问队列,被阻塞的线程能够按照阻塞的前后顺序访问队列,
      • 即先阻塞的线程先访问队列。
    • 而非公平队列,当队列可用时,阻塞的线程将进入争夺访问资源的竞争中,
      • 也就是说谁先抢到谁就执行,没有固定的前后顺序。
    • //默认非公平阻塞队列
      ArrayBlockingQueue queue = new ArrayBlockingQueue(2);
      //公平阻塞队列
      ArrayBlockingQueue queue1 = new ArrayBlockingQueue(2,true);
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    /** 存储数据的数组 */
    final Object[] items;

    /**获取数据的索引,主要用于take,poll,peek,remove方法 */
    int takeIndex;

    /**添加数据的索引,主要用于 put, offer, or add 方法*/
    int putIndex;

    /** 队列元素的个数 */
    int count;


    /** 控制并不是访问的锁 */
    final ReentrantLock lock;

    /**notEmpty条件对象,用于通知take方法队列已有元素,可执行获取操做 */
    private final Condition notEmpty;

    /**notFull条件对象,用于通知put方法队列未满,可执行添加操做 */
    private final Condition notFull;

    /**
       迭代器
     */
    transient Itrs itrs = null;

}
  • ArrayBlockingQueue内部确实是经过数组对象items来存储全部的数据
    • 一个ReentrantLock来同时控制添加线程移除线程的并发访问,
      • 这点与LinkedBlockingQueue区别很大(稍后会分析)
    • notEmpty条件对象则是用于存放等待唤醒调用take方法的线程,
      • 告诉他们队列已有元素,能够执行获取操做
    • notFull条件对象是用于等待或唤醒调用put方法的线程,
      • 告诉它们,队列未满,能够执行添加元素的操做
    • 只要putIndex与takeIndex不相等就说明队列没有结束

LinkedBlockingQueuenode

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    /**
     * 节点类,用于存储数据
     */
    static class Node<E> {
        E item;

        /**
         * One of:
         * - the real successor Node
         * - this Node, meaning the successor is head.next
         * - null, meaning there is no successor (this is the last node)
         */
        Node<E> next;

        Node(E x) { item = x; }
    }

    /** 阻塞队列的大小,默认为Integer.MAX_VALUE */
    private final int capacity;

    /** 当前阻塞队列中的元素个数 */
    private final AtomicInteger count = new AtomicInteger();

    /**
     * 阻塞队列的头结点
     */
    transient Node<E> head;

    /**
     * 阻塞队列的尾节点
     */
    private transient Node<E> last;

    /** 获取并移除元素时使用的锁,如take, poll, etc */
    private final ReentrantLock takeLock = new ReentrantLock();

    /** notEmpty条件对象,当队列没有数据时用于挂起执行删除的线程 */
    private final Condition notEmpty = takeLock.newCondition();

    /** 添加元素时使用的锁如 put, offer, etc */
    private final ReentrantLock putLock = new ReentrantLock();

    /** notFull条件对象,当队列数据已满时用于挂起执行添加的线程 */
    private final Condition notFull = putLock.newCondition();

}
  • 由链表实现的有界队列阻塞队列,但大小默认值为Integer.MAX_VALUE
    • 因此咱们在使用LinkedBlockingQueue时建议手动传值,
    • 为其提供咱们所需的大小,
    • 避免队列过大形成机器负载或者内存爆满等状况。
      • 若是存在添加速度大于删除速度时候,
      • 有可能会内存溢出,这点在使用前但愿慎重考虑
  • 在正常状况下,连接队列的吞吐量要高于基于数组的队列(ArrayBlockingQueue)
    • 由于其内部实现添加删除操做使用的两个ReenterLock来控制并发执行,
  • 内部维持一个基于链表的数据队列
    • 每一个添加到LinkedBlockingQueue队列中的数据都将被封装成Node节点,
    • 添加的链表队列中,其中head和last分别指向队列的头结点和尾结点
    • 内部分别使用了takeLock 和 putLock 对并发进行控制,
      • 也就是说,添加和删除操做并非互斥操做,
      • 能够同时进行,这样也就能够大大提升吞吐量
  • remove方法删除指定的对象,为何同时对putLock和takeLock加锁?
    • remove(Object o)
      • 删除指定元素
    • 这是由于remove方法删除的数据的位置不肯定,
    • 为了不形成并不是安全问题,因此须要对2个锁同时加锁。
    • 其余移除队尾的操做,一个takeLock锁够了
相关文章
相关标签/搜索