ConcurrentLinkedQueue

本文介绍了ConcurrentLinkedQueue,一个基于链表实现的线程安全无界队列,使用CAS操作保证并发安全性。它适用于高性能多线程读写场景,重点分析了其工作原理和offer方法的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

介绍

非阻塞队列。高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,通过 CAS 操作实现。

ConcurrentLinkedQueue实际对应的是LinkedList,是一个线程安全的无界队列,但LinkedList是一个双向链表,而ConcurrentLinkedQueue是单向链表。

ConcurrentLinkedQueue线程安全在于设置head、tail以及next指针时都用的cas操作,而且node里的item和next变量都是用volatile修饰,保证了多线程下变量的可见性。而ConcurrentLinkedQueue的所有读操作都是无锁的,所以可能读会存在不一致性。

如果对队列加锁的成本较高则适合使用无锁的 ConcurrentLinkedQueue 来替代。适合在对性能要求相对较高,同时有多个线程对队列进行读写的场景。

底层源码

初始属性

//head节点存放链表第一个item为null的节点,但tail则并不是总指向最后一个节点
private transient volatile Node<E> head;

private transient volatile Node<E> tail;

//Node中主要包含两个属性, 节点元素( item )和指向下一个节 点( next )的引用 
private static class Node<E> {
    volatile E item;
    volatile Node<E> next;
}

head和tail是volatile类型,具有 volatile 赋予的含义:“即对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入”。

Node是单向链表节点,next指向下一个Node,item用于存储数据。Node中操作节点数据的API,是通过Unsafe机制的CAS函数实现的;例如casNext()是通过CAS函数“比较并设置节点的下一个节点”。

构造器

//新建了一个“内容为null的节点”,并设置表头head和表尾tail的值为新节点。
public ConcurrentLinkedQueue() {
    head = tail = new Node<E>(null);
}

入队offer方法

public boolean offer(E e) {
    // 检查e是不是null,是的话抛出NullPointerException异常。
    checkNotNull(e);
    // 创建新的节点
    final Node<E> newNode = new Node<E>(e);

    // 将“新的节点”添加到链表的末尾。
    for (Node<E> t = tail, p = t;;) {//这个for循环是个死循环,增加了两个指针p,t。
        Node<E> q = p.next;
        // 情况1:q为空,p就是尾节点,新节点插入
        if (q == null) {
            // CAS操作:如果“p的下一个节点为null”(即p为尾节点),则设置p的下一个节点为newNode。
            // 如果该CAS操作成功的话,则比较“p和t”(若p不等于t,则设置newNode为新的尾节点),然后返回true。
            // 如果该CAS操作失败,这意味着“其它线程对尾节点进行了修改”,则重新循环。
            if (p.casNext(null, newNode)) {
                if (p != t) // hop two nodes at a time
                    casTail(t, newNode);  // Failure is OK.
                return true;
            }
        }
        // 情况2:p和q相等
        else if (p == q)
            p = (t != (t = tail)) ? t : head;
        // 情况3:其它\\
		//这里是移动p指针,意思就是此时如果p不是最后一个元素则把p指针指向tail,否则指向q,也就是指向p.next元素。
        else
            p = (p != t && t != (t = tail)) ? t : q;
    }
}

总结:

非阻塞队列中的几种主要方法:offer(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false; poll():移除并获取队首元素,若成功,则返回队首元素;否则返回null; peek():获取队首元素,若成功,则返回队首元素;否则返回null对于非阻塞队列,

对于非阻塞队列,一般情况下建议使用offer、poll和peek三个方法,不建议使用add和remove方法。因为使用offer、poll和peek三个方法可以通过返回值判断操作成功与否,而使用add和remove方法却不能达到这样的效果

offer过程实际就是不停就是在单链表不断挂新元素的过程,挂next元素时使用了cas来保证线程的安全性,而且不是每次offer都会移动tail指针,而是tail已经不是现在的最后一个node时才会移动tail指针

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值