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?
  • 相关阅读:
    PAT 1010. 一元多项式求导 (25)
    PAT 1009. 说反话 (20) JAVA
    PAT 1009. 说反话 (20)
    PAT 1007. 素数对猜想 (20)
    POJ 2752 Seek the Name, Seek the Fame KMP
    POJ 2406 Power Strings KMP
    ZOJ3811 Untrusted Patrol
    Codeforces Round #265 (Div. 2) 题解
    Topcoder SRM632 DIV2 解题报告
    Topcoder SRM631 DIV2 解题报告
  • 原文地址:https://www.cnblogs.com/arax/p/9928066.html
Copyright © 2011-2022 走看看