`
wbj0110
  • 浏览: 1611431 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

java-ConcurrentLinkedQueue

    博客分类:
  • Java
阅读更多

ConcurrentLinkedQueue是Queue的一个线程安全实现。先来看一段文档说明。

一个基于链接节点的无界线程安全队列。此队列按照 FIFO(先进先出)原则对元素进行排序。队列的头部 是队列中时间最长的元素。队列的尾部 是队列中时间最短的元素。新的元素插入到队列的尾部,队列获取操作从队列头部获得元素。当多个线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许使用 null 元素。

 

由于ConcurrentLinkedQueue只是简单的实现了一个队列Queue,因此从API的角度讲,没有多少值的介绍,使用起来也很简单,和前面遇到的所有FIFO队列都类似。出队列只能操作头节点,入队列只能操作尾节点,任意节点操作就需要遍历完整的队列。

重点放在解释ConcurrentLinkedQueue的原理和实现上。

 

在继续探讨之前,结合前面线程安全的相关知识,我来分析设计一个线程安全的队列哪几种方法。

第一种:使用synchronized同步队列,就像Vector或者Collections.synchronizedList/Collection那样。显然这不是一个好的并发队列,这会导致吞吐量急剧下降。

第二种:使用Lock。一种好的实现方式是使用ReentrantReadWriteLock来代替ReentrantLock提高读取的吞吐量。但是显然ReentrantReadWriteLock的实现更为复杂,而且更容易导致出现问题,另外也不是一种通用的实现方式,因为ReentrantReadWriteLock适合哪种读取量远远大于写入量的场合。当然了ReentrantLock是一种很好的实现,结合Condition能够很方便的实现阻塞功能,这在后面介绍BlockingQueue的时候会具体分析。

第三种:使用CAS操作。尽管Lock的实现也用到了CAS操作,但是毕竟是间接操作,而且会导致线程挂起。一个好的并发队列就是采用某种非阻塞算法来取得最大的吞吐量。

ConcurrentLinkedQueue采用的就是第三种策略。它采用了参考资料1 中的算法。

在锁机制中谈到过,要使用非阻塞算法来完成队列操作,那么就需要一种“循环尝试”的动作,就是循环操作队列,直到成功为止,失败就会再次尝试。这在前面的章节中多次介绍过。

 

针对各种功能深入分析。

在开始之前先介绍下ConcurrentLinkedQueue的数据结构。

image在上面的数据结构中,ConcurrentLinkedQueue只有头结点、尾节点两个元素,而对于一个节点Node而言除了保存队列元素item外,还有一个指向下一个节点的引用next。 看起来整个数据结构还是比较简单的。但是也有几点是需要说明:

  1. 所有结构(head/tail/item/next)都是volatile类型。 这是因为ConcurrentLinkedQueue是非阻塞的,所以只有volatile才能使变量的写操作对后续读操作是可见的(这个是有happens-before法则保证的)。同样也不会导致指令的重排序。
  2. 所有结构的操作都带有原子操作,这是由AtomicReferenceFieldUpdater保证的,这在原子操作中介绍过。它能保证需要的时候对变量的修改操作是原子的。
  3. 由于队列中任何一个节点(Node)只有下一个节点的引用,所以这个队列是单向的,根据FIFO特性,也就是说出队列在头部(head),入队列在尾部(tail)。头部保存有进入队列最长时间的元素,尾部是最近进入的元素。
  4. 没有对队列长度进行计数,所以队列的长度是无限的,同时获取队列的长度的时间不是固定的,这需要遍历整个队列,并且这个计数也可能是不精确的。
  5. 初始情况下队列头和队列尾都指向一个空节点,但是非null,这是为了方便操作,不需要每次去判断head/tail是否为空。但是head却不作为存取元素的节点,tail在不等于head情况下保存一个节点元素。也就是说head.item这个应该一直是空,但是tail.item却不一定是空(如果head!=tail,那么tail.item!=null)。

对于第5点,可以从ConcurrentLinkedQueue的初始化中看到。这种头结点也叫“伪节点”,也就是说它不是真正的节点,只是一标识,就像c中的字符数组后面的\0以后,只是用来标识结束,并不是真正字符数组的一部分。

private transient volatile Node<E> head = new Node<E>(null, null);
private transient volatile Node<E> tail = head;

有了上述5点再来解释相关API操作就容易多了。

在上一节中列出了add/offer/remove/poll/element/peek等价方法的区别,所以这里就不再重复了。

清单1 入队列操作

public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    Node<E> n = new Node<E>(e, null);
    for (;;) {
        Node<E> t = tail;
        Node<E> s = t.getNext();
        if (t == tail) {
            if (s == null) {
                if (t.casNext(s, n)) {
                    casTail(t, n);
                    return true;
                }
            } else {
                casTail(t, s);
            }
        }
    }
}

清单1 描述的是入队列的过程。整个过程是这样的。

    1. 获取尾节点t,以及尾节点的下一个节点s。如果尾节点没有被别人修改,也就是t==tail,进行2,否则进行1。
    2. 如果s不为空,也就是说此时尾节点后面还有元素,那么就需要把尾节点往后移,进行1。否则进行3。
    3. 修改尾节点的下一个节点为新节点,如果成功就修改尾节点,返回true。否则进行1。

从操作3中可以看到是先修改尾节点的下一个节点,然后才修改尾节点位置的,所以这才有操作2中为什么获取到的尾节点的下一个节点不为空的原因。

特别需要说明的是,对尾节点的tail的操作需要换成临时变量t和s,一方面是为了去掉volatile变量的可变性,另一方面是为了减少volatile的性能影响。

 

清单2 描述的出队列的过程,这个过程和入队列相似,有点意思。

头结点是为了标识队列起始,也为了减少空指针的比较,所以头结点总是一个item为null的非null节点。也就是说head!=null并且head.item==null总是成立。所以实际上获取的是head.next,一旦将头结点head设置为head.next成功就将新head的item设置为null。至于以前就的头结点h,h.item=null并且h.next为新的head,但是由于没有对h的引用,所以最终会被GC回收。这就是整个出队列的过程。

清单2 出队列操作

public E poll() {
    for (;;) {
        Node<E> h = head;
        Node<E> t = tail;
        Node<E> first = h.getNext();
        if (h == head) {
            if (h == t) {
                if (first == null)
                    return null;
                else
                    casTail(t, first);
            } else if (casHead(h, first)) {
                E item = first.getItem();
                if (item != null) {
                    first.setItem(null);
                    return item;
                }
                // else skip over deleted item, continue loop,
            }
        }
    }
}

 

另外对于清单3 描述的获取队列大小的过程,由于没有一个计数器来对队列大小计数,所以获取队列的大小只能通过从头到尾完整的遍历队列,显然这个代价是很大的。所以通常情况下ConcurrentLinkedQueue需要和一个AtomicInteger搭配才能获取队列大小。后面介绍的BlockingQueue正是使用了这种思想。

清单3 遍历队列大小

public int size() {
    int count = 0;
    for (Node<E> p = first(); p != null; p = p.getNext()) {
        if (p.getItem() != null) {
            // Collections.size() spec says to max out
            if (++count == Integer.MAX_VALUE)
                break;
        }
    }
    return count;
}

 

 

 

 

参考资料:

  1. Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms
  2. 多线程基础总结十一—ConcurrentLinkedQueue
  3. 对ConcurrentLinkedQueue进行的并发测试 

http://www.blogjava.net/xylz/archive/2010/07/23/326934.html

 

分享到:
评论

相关推荐

    Java 多线程与并发(15-26)-JUC集合- ConcurrentLinkedQueue详解.pdf

    ### Java多线程与并发(15-26)-JUC集合-ConcurrentLinkedQueue详解 #### 一、ConcurrentLinkedQueue概述 `ConcurrentLinkedQueue`是Java实用工具包(J.U.C)中的一个高性能线程安全队列,主要用于解决多线程环境下...

    Java--collection.rar_SEP_java 集合

    6. **LinkedBlockingQueue** 和 **ConcurrentLinkedQueue** 是线程安全的队列实现,常用于多线程环境下的并发编程,它们实现了`Queue`接口。 7. **Collections** 类是集合框架的一个工具类,提供了静态方法来操作...

    java-chatroom.zip_java 聊天室_聊天室

    Java 提供了 synchronized 关键字、Lock 接口(如 ReentrantLock)以及各种并发工具类(如 ConcurrentLinkedQueue)来保证数据一致性。 6. **设计模式**:在构建聊天室时,可能会用到如单例模式(确保服务器实例的...

    Java-jdk10-最新最全多线程编程实战指南-核心篇

    4. **并发集合**:讲解ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentLinkedQueue等并发安全的集合类,以及它们在多线程环境下的性能优势。 5. **线程通信**:介绍wait()、notify()和notifyAll()方法,以及...

    JAVA-多线程 所有文件

    13. **并发集合**:Java并发库提供了一些线程安全的集合,如`ConcurrentHashMap`, `CopyOnWriteArrayList`, `ConcurrentLinkedQueue`等,它们在多线程环境下表现更优。 在实际项目中,理解和熟练运用这些多线程知识...

    Java-C-JS数据结构与算法合集

    - Java的并发库`java.util.concurrent`提供了线程安全的数据结构,如ConcurrentHashMap、ConcurrentLinkedQueue等。 4. **C语言中的数据结构与算法**: - C语言通过指针实现了灵活的数据结构,如链表、树、图等。...

    使用-Java-构造高可扩展应用

    使用 Java 构造高可扩展应用需要遵循一些简单规则和工具,例如使用 LockFreeQueue 替换标准库中的 ConcurrentLinkedQueue,检测锁的使用和冲突,等等。本文提供了一些实用的经验和建议,帮助开发人员提高 Java 多...

    java-concurrency编程内部分享_java实战_java_

    书中还讨论了并发集合,如`ConcurrentHashMap`、`CopyOnWriteArrayList`和`ConcurrentLinkedQueue`,这些集合类在设计上已经考虑了并发环境下的安全性,能有效提高并发性能。此外,`Atomic`类(如`AtomicInteger`、`...

    计算机后端-Java-Java高并发从入门到面试教程-务降级与服.zip

    2. **并发容器**:如ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentLinkedQueue等,它们为并发操作提供了线程安全的解决方案。 3. **线程池**:ExecutorService和ThreadPoolExecutor的理解,如何配置线程池...

    LinkedBlockingQueue 和 ConcurrentLinkedQueue的区别.docx

    LinkedBlockingQueue和ConcurrentLinkedQueue是Java并发包中两个常用的线程安全队列,它们各有特点,适用于不同的场景。本文将深入探讨两者之间的差异以及如何根据需求选择合适的队列。 首先,LinkedBlockingQueue...

    ConcurrentLinkedQueue源码分析.rar

    在Java并发编程领域,`ConcurrentLinkedQueue`是一个非常重要的数据结构,它是线程安全的无界队列,基于链接节点实现,性能高效。本篇文章将深入探讨`ConcurrentLinkedQueue`的设计原理、内部结构以及其源码中的关键...

    java-multithreading:java-多线程

    `java.util.concurrent` 包提供了线程安全的集合,如 `ConcurrentHashMap`、`CopyOnWriteArrayList`、`ConcurrentLinkedQueue` 等。 10. **线程死锁** 多个线程相互等待对方释放资源,导致都无法继续执行的状态。...

    the-art-of-java-concurrency-programming:Java并发编程的艺术原始代码

    7. **并发集合类**:如ConcurrentHashMap、CopyOnWriteArrayList和ConcurrentLinkedQueue等,这些集合在设计时考虑了并发环境下的性能和安全性。 8. **线程局部变量**:ThreadLocal为每个线程提供独立的变量副本,...

    java高并发源码-java-concurrent:Java高并发,JUC,相关源码。1、马士兵高并发视频源码(听课时练习)

    7. Concurrent Collections:如ConcurrentHashMap、ConcurrentLinkedQueue等,是一系列线程安全的数据结构,能够在高并发下提供高效性能。 8. CountDownLatch、CyclicBarrier和Semaphore:这些是同步辅助类,用于...

    java-concurrency-in-practice-exercises:看书时的一些练习

    9. **并发集合类**:Java并发包提供了一系列线程安全的集合类,如`ConcurrentHashMap`、`CopyOnWriteArrayList`、`ConcurrentLinkedQueue`等,这些集合在多线程环境下能够保证数据一致性。 10. **死锁**:当两个或...

    并发队列ConcurrentLinkedQueue和阻塞队列LinkedBlockingQueue用法

    `ConcurrentLinkedQueue` 是 Java 并发包 `java.util.concurrent` 提供的一个高性能的线程安全队列实现,基于链表结构,它适用于对吞吐量有较高要求的场景。`ConcurrentLinkedQueue` 不提供容量限制,并且在队列为空...

    Java-Concurrency:Java并发学习演示

    6. **并发集合**:Java并发库提供了一些线程安全的集合,如`ConcurrentHashMap`, `CopyOnWriteArrayList`, `ConcurrentLinkedQueue`等,它们内部实现了线程安全的算法,允许在并发环境下高效操作。 7. **Executor...

    java数据结构源码-java-datastructures:按源版本划分的Java数据结构实现细节

    Java并发包`java.util.concurrent`提供了线程安全的数据结构,如ConcurrentHashMap、ConcurrentLinkedQueue等,它们在多线程环境下表现出色。 8. 原生数据结构 Java原生数据结构如byte[]、char[]等,直接在内存中...

    java-concurrent-ways:演示几种用Java实现并发的方法

    7. **并发集合**:Java提供了一些线程安全的集合,如`ConcurrentHashMap`, `CopyOnWriteArrayList`, `ConcurrentLinkedQueue`等,它们在多线程环境下性能优越。 8. **并发工具类**:`java.util.concurrent`包中包含...

    并行计算框架的Java实现--系列二

    另一个重要的概念是并发数据结构,如`ConcurrentHashMap`、`ConcurrentLinkedQueue`等,它们在多线程环境下提供了高效且线程安全的操作。这些数据结构内部实现了锁和其他同步机制,允许在不牺牲性能的前提下进行并发...

Global site tag (gtag.js) - Google Analytics