zoukankan      html  css  js  c++  java
  • ArrayBlockingQueue与LinkedBlockingQueue对比

    ps:在下文中分别以Array代表ArrayBlockingQueue,Link代表LinkedBlockingQueue,下文中不再说明。

    Array和Link在并发场景中经常使用,他们的共同作用就是实现线程安全队列。下面对这两种队列的实现进行对比分析。

    底层实现

    ArrayBlockingQueue

    • 底层基于数组实现,在对象创建时需要指定数组大小。在构建对象时,已经创建了数组。所以使用Array需要特别注意设定合适的队列大小,如果设置过大会造成内存浪费。如果设置内存太小,就会影响并发的性能。
    • 功能上,其内部维护了两个索引指针putIndex和takeIndex。putIndex表示下次调用offer时存放元素的位置,takeIndex表示的时下次调用take时获取的元素。有了这两个索引的支持后,还是无法说明白其底层的实现原理。那么我们来看一段其内部出现最多的代码:
        int i = takeIndex;
        ...
        if (++i == items.length)
            i = 0;
        ...
    

    这几行在代码在Array中几乎每个函数都会用到。意思不管是在读取元素,或者存放元素,如果到达数组的最后一个元素,直接将索引移动到第一个位置。你可能会想,如果我一直往队列中添加元素而不取,添加的元素个数超过了数组长度,会不会覆盖之前添加的元素。在实际使用过程中是不会出现这种情况的,其内部使用了ReentrantLock的Condition,这部分在并发支持中介绍。

    LinkedBlockingQueue

    • 底层基于单向链表实现。实现了队列的功能,元素到来放到链表头,从链表尾部取取数据。这种数据结构没有必要使用双向链表。链表的好处(数组的没有的)是不用提前分配内存。Link也支持在创建对象时指定队列长度,如果没有指定,默认为Integer.MAX_VALUE。

    并发支持

    最大的区别就是Array内部只有一把锁,offer和take使用同一把锁,而Link的offer和take使用不同的锁。

    ReentrantLock和其Condition的关系

    在做具体分析之前,先介绍一下ReentrantLock 和其Condition之间的关系。ReentrantLock内部维护了一个双向链表,链表上的每个节点都会保存一个线程,锁在双向链表的头部自选,取出线程执行。而Condition内部同样维持着一个双向链表,但是其向链表中添加元素(await)和从链表中移除(signal)元素没有像ReentrantLock那样,保证线程安全,所以在调用Condition的await()和signal()方法时,需要在lock.lock()和lock.unlock()之间以保证线程的安全。在调用Condition的signal时,它从自己的双向链表中取出一个节点放到了ReentrantLock的双向链表中,所以在具体的运行过程中不管ReentrantLock new 了几个Condition其实内部公用的一把锁。介绍完这个之后,我么来分析ArrayBlockingQueue和LinkedBlockingQueue的内部实现不同。

    ArrayBlockingQueue

    先看其内部锁的定义:

        int count;
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    
    • lock 其内部的锁
    • notEmpty 当调用offer时,会调用notEmpty.signal() 通知之前因为队列空而被阻塞的线程。同时在take后,如果内部计数器count=0时,会调用notEmpty.await() 阻塞调用take的线程。
    • notFull 当调用offer时,如果现在count=内部数组的长度时,会调用notFull.await()阻塞现在添加元素的所有线程;当调用take时,总会调用notFull.signal()唤醒之前因为队列满而阻塞的线程。

    根据上面分析ReentrantLock和其Condition的关系,可以看到放元素和取元素用的同一把锁,无法使放元素和取元素同时进行,只能先后相继执行。

    LinkedBlockingQueue

    内部锁定义:

        /** Current number of elements */
        private final AtomicInteger count = new AtomicInteger();
    
        /** Lock held by take, poll, etc */
        private final ReentrantLock takeLock = new ReentrantLock();
    
        /** Wait queue for waiting takes */
        private final Condition notEmpty = takeLock.newCondition();
    
        /** Lock held by put, offer, etc */
        private final ReentrantLock putLock = new ReentrantLock();
    
        /** Wait queue for waiting puts */
        private final Condition notFull = putLock.newCondition();
    
    • count 内部元素计数器使用的原子类型的计数器,使的元素个数的更新支持并发,为下面取和放元素并发提供了支持。
    • takeLock 取元素单独的锁,和放元素分开,这样即使有Condition也可以使的取和放元素在不同的节点上自选
    • notEmpty 取元素的Condition锁,和放元素锁分开。
    • putLock notFull 和上面介绍的takeLock notEmpty一直。

    通过这种设置,可以将在链表头上放元素和在链表尾部取元素不再竞争锁,在一定程度上可以加快数据处理。

    I am chris, and what about you?
  • 相关阅读:
    【html5构建触屏网站】之touch事件
    优化网站加载速度的14个技巧
    存储
    [概念] javascript构造函数和普通函数的
    nodejs中的 Cannot read property'text' of undefined 问题
    整理js继承
    清除浮动的五种方法
    用canvas绘制一个时钟
    javascript运动框架
    $(document).ready()与window.onload的区别(转发)
  • 原文地址:https://www.cnblogs.com/arax/p/9928066.html
Copyright © 2011-2022 走看看