ArrayBlockingQueue 与 LinkedBlockingQueue

https://blog.csdn.net/javazejian/article/details/77410889

  • 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不相等就说明队列没有结束

LinkedBlockingQueue

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锁够了

转载于:https://my.oschina.net/u/3847203/blog/3089101

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值