跳转到内容

JUC并发集合-ConcurrentLinkedQueue

草稿难度:中级#集合#JUC

原文:CSDN(历史文章导入,当前状态为草稿)

ConcurrentLinkedQueue是Java并发包(java.util.concurrent)中提供的一个线程安全的无界非阻塞队列,基于链表数据结构实现。它实现了Queue接口,遵循 FIFO (先进先出)原则,专为高并发环境设计。

作为一个非阻塞队列,ConcurrentLinkedQueue的操作永远不会导致线程阻塞。即使队列为空或者已满,相关操作也会立即返回,而不是等待条件满足。这种特性使其在高并发场景下表现出色,能够有效减少线程等待和上下文 切换 的开销。

1.2 ConcurrentLinkedQueue与BlockingQueue的区别

Section titled “1.2 ConcurrentLinkedQueue与BlockingQueue的区别”

为了更好地理解ConcurrentLinkedQueue的特点,我们将其与Java中的BlockingQueue进行对比:

特性 ConcurrentLinkedQueue BlockingQueue
阻塞特性 非阻塞,操作永不阻塞线程 可能阻塞线程(如take()、put()方法)
API 提供offer()/poll()/peek()等非阻塞方法 增加了put()/take()等可能阻塞的方法
边界特性 无界队列,理论上可以无限扩展 有有界实现(如ArrayBlockingQueue)和无界实现(如LinkedBlockingQueue)
实现原理 基于CAS等非阻塞算法 通常基于锁实现(如ReentrantLock)
适用场景 追求高吞吐量,不希望线程阻塞的场景 需要流量控制,生产者消费者速率不匹配的场景

从上表可以看出,ConcurrentLinkedQueue与BlockingQueue的主要区别在于其非阻塞特性和实现机制。这些差异导致它们适用于不同的应用场景。

根据ConcurrentLinkedQueue的特性,以下场景特别适合使用它:

  1. 追求高吞吐量的场景:由于无锁设计,ConcurrentLinkedQueue在高并发环境下能提供更高的吞吐量,特别是当操作很短暂时。
  2. 生产者消费者速率相当,不希望任一方被阻塞的场景:当生产和消费速率基本匹配,不需要通过阻塞来调节速率时,非阻塞队列更为高效。
  3. 对响应时间敏感,不能接受线程阻塞的场景:如实时系统、UI线程等,需要保持响应性,不能容忍线程长时间阻塞。
  4. 多个生产者和消费者并发访问队列的场景:ConcurrentLinkedQueue专为高并发设计,多线程并发访问时性能优异。

具体应用场景包括:

  • 事件处理系统
  • 消息传递系统
  • 任务分发系统
  • 日志收集系统
  • 缓冲区实现

非阻塞队列的核心原理是:操作永远不会导致线程阻塞,而是立即返回结果,即使操作无法完成(如队列为空时的出队操作)。

ConcurrentLinkedQueue作为一个非阻塞队列,具有以下特点:

  1. 无等待:操作不会导致线程等待,即使队列为空或已满
  2. 立即返回:操作总是立即返回,返回值表示操作是否成功
  3. 无界性:理论上可以无限扩展,只受系统内存限制
  4. 无锁设计:使用CAS等非阻塞算法代替传统锁

这种设计带来的好处是:

  • 减少线程阻塞和上下文切换的开销
  • 避免死锁和优先级倒置问题
  • 提高系统的整体吞吐量
  • 降低线程调度的复杂性

但也有一些限制:

  • 不提供阻塞等待功能,需要自行实现轮询或其他机制
  • 不提供容量限制,需要自行控制队列大小
  • 可能导致CPU资源浪费(如忙等待)

ConcurrentLinkedQueue内部基于单向链表实现,其核心数据结构包括:

  1. Node类:表示链表节点,包含两个主要字段:

    private static class Node<E> {
    volatile E item; // 存储的元素,使用volatile保证可见性
    volatile Node<E> next; // 指向下一个节点的引用,使用volatile保证可见性
    // 构造方法
    Node(E item) {
    UNSAFE.putObject(this, itemOffset, item);
    }
    // CAS操作方法
    boolean casItem(E cmp, E val) {
    return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
    }
    boolean casNext(Node<E> cmp, Node<E> val) {
    return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
    }
    // 其他方法...
    }
  2. 队列引用:队列维护head和tail两个引用,分别指向队列的头和尾:

    private transient volatile Node<E> head;
    private transient volatile Node<E> tail;

这种简单的数据结构设计有几个关键特点:

  1. volatile修饰:Node类的item和next字段都使用volatile修饰,确保多线程环境下的可见性。

  2. 松弛不变量

    • head不严格指向第一个节点(可能指向已删除的节点)
    • tail不严格指向最后一个节点(可能存在未被tail引用的尾节点)
    • 这种设计减少了CAS操作次数,提高了性能
  3. 单向链表:只有next引用,没有prev引用,简化了设计但也限制了某些操作

  4. 无哨兵节点:不使用哨兵节点,第一个节点就包含实际数据

下面是ConcurrentLinkedQueue的简化结构示意图:

head tail
| |
v v
[Node] -> [Node] -> [Node] -> [Node] -> [Node] -> null

在实际运行中,由于松弛不变量的设计,head可能指向已删除的节点,tail可能落后于真正的尾节点,如下所示:

head tail
| |
v v
[Node*] -> [Node] -> [Node] -> [Node] -> [Node] -> [Node] -> null
* 节点的item已设为null,表示已删除

ConcurrentLinkedQueue采用了无锁并发控制机制,主要包括以下几个方面:

  1. CAS(Compare-And-Swap)操作

    • 使用Unsafe类提供的compareAndSwapObject方法实现原子更新
    • 主要用于更新节点的item和next引用
    • 确保在多线程环境下对共享引用的安全更新
    // CAS更新节点的item字段
    boolean casItem(E cmp, E val) {
    return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
    }
    // CAS更新节点的next字段
    boolean casNext(Node<E> cmp, Node<E> val) {
    return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
    }
  2. volatile关键字

    • Node类的item和next字段使用volatile修饰
    • 队列的head和tail引用使用volatile修饰
    • 保证多线程间的可见性,一个线程的修改对其他线程立即可见
    private transient volatile Node<E> head;
    private transient volatile Node<E> tail;
  3. 不变性设计

    • 入队操作只修改尾节点及其next引用
    • 出队操作只修改头节点及其item值
    • 这种分离设计减少了线程间的竞争
  4. 松弛不变量(Relaxed Invariants)

    • head不一定是第一个节点,可能有一些被移除的节点在它前面
    • tail不一定是最后一个节点,可能有一些新添加的节点在它后面
    • 这种松弛设计减少了CAS操作次数,提高了性能
  5. 惰性删除

    • 出队时只将节点的item设为null,不立即删除节点本身
    • 节点的物理删除是延迟的、增量的
    • 减少了内存分配和GC压力
  6. “指向自身”标记

    • 使用节点的next指向自身来标记节点已被删除
    • 防止其他线程误用已删除的节点

这些无锁机制共同作用,确保了ConcurrentLinkedQueue在高并发环境下的安全性和高性能。

ConcurrentLinkedQueue的松弛不变量设计是其高性能的关键因素之一,下面详细分析这一设计:

  1. 头节点松弛

    • 传统队列实现通常要求head严格指向第一个有效节点
    • ConcurrentLinkedQueue允许head指向已删除的节点(item为null的节点)
    • 只有在必要时(如poll操作遇到多个连续的已删除节点)才更新head引用
    • 这减少了对head引用的竞争和CAS操作次数
  2. 尾节点松弛

    • 传统队列实现通常要求tail严格指向最后一个节点
    • ConcurrentLinkedQueue允许tail落后于真正的尾节点
    • 只有当发现tail至少落后一个节点时,才尝试更新tail引用
    • 这减少了对tail引用的竞争和CAS操作次数
  3. 松弛不变量的好处

    • 减少CAS操作次数,降低竞争
    • 分摊更新head和tail的成本
    • 提高高并发环境下的吞吐量
    • 简化实现,减少复杂的同步逻辑
  4. 松弛不变量的代价

    • 某些操作可能需要额外的遍历步骤
    • size()方法变得更加复杂和低效
    • 增加了代码理解的难度

松弛不变量设计体现了 并发编程 中的一个重要思想:通过放宽不变量约束,可以减少同步操作,提高并发性能。这种设计在高并发、高吞吐量场景下特别有价值。

ConcurrentLinkedQueue提供了两个构造方法:

// 默认构造方法
public ConcurrentLinkedQueue() {
head = tail = new Node<E>(null);
}
// 从已有集合构造
public ConcurrentLinkedQueue(Collection<? extends E> c) {
Node<E> h = null, t = null;
for (E e : c) {
checkNotNull(e);
Node<E> newNode = new Node<E>(e);
if (h == null)
h = t = newNode;
else {
t.lazySetNext(newNode);
t = newNode;
}
}
if (h == null)
h = t = new Node<E>(null);
head = h;
tail = t;
}

源码分析

  1. 默认构造方法创建一个空队列,初始化包含一个item为null的哨兵节点
  2. 从集合构造的方法会遍历集合中的元素,创建对应的节点并链接起来
  3. 如果集合为空,同样会创建一个包含null的哨兵节点
  4. 构造完成后,head指向第一个节点,tail指向最后一个节点

这两个构造方法都很简单,主要是 初始化 队列的基本结构。值得注意的是,从集合构造的方法使用了lazySetNext方法而不是直接设置next引用,这是一种优化,减少了内存屏障的使用。

offer方法用于将元素添加到队列尾部,是ConcurrentLinkedQueue的核心入队操作:

public boolean offer(E e) {
checkNotNull(e);
final Node<E> newNode = new Node<E>(e);
// 从尾节点开始尝试插入
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
// 如果p的next为null,说明p是最后一个节点,尝试插入
if (q == null) {
// 使用CAS操作尝试更新p的next引用指向新节点
if (p.casNext(null, newNode)) {
// 成功插入节点
// 如果p不是尾节点t,或者t是初始节点,尝试更新tail
if (p != t)
casTail(t, newNode); // 更新失败也没关系
return true;
}
// CAS失败,说明有并发修改,重新获取p的next
}
// 如果p的next指向自己,说明p已被删除,需要从头开始
else if (p == q)
p = (t != (t = tail)) ? t : head;
// 否则,向前移动p
else
p = (p != t && t != (t = tail)) ? t : q;
}
}

源码分析

  1. 首先检查元素不为null,然后创建新节点
  2. 从尾节点开始尝试插入,使用无限循环直到成功
  3. 如果当前节点p的next为null,说明p可能是最后一个节点:
    • 使用CAS操作尝试将p的next指向新节点
    • 如果成功,可能还需要更新tail引用(但这不是必须的,体现了松弛不变量)
    • 如果失败,说明有并发修改,需要重试
  4. 如果p的next指向自己,说明p已被删除,需要重新定位:
    • 如果tail已更新,从新的tail开始
    • 否则从head开始
  5. 如果p不是最后一个节点,向前移动p继续查找

offer方法的实现体现了ConcurrentLinkedQueue的几个关键设计思想:

  • 使用CAS操作保证线程安全
  • 松弛不变量设计(tail不一定指向最后节点)
  • 处理并发修改和已删除节点的情况
  • 无限重试直到成功

poll方法用于从队列头部移除并返回一个元素,是ConcurrentLinkedQueue的核心出队操作:

public E poll() {
restartFromHead:
for (;;) {
// 从头节点开始
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
// 如果p的item不为null,尝试将其设为null(移除元素)
if (item != null && p.casItem(item, null)) {
// 成功移除元素
// 如果p不是头节点h,更新head跳过已删除的节点
if (p != h)
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
// 如果p的next为null,说明队列为空
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
// 如果p的next指向自己,说明p已被删除,需要从头开始
else if (p == q)
continue restartFromHead;
// 否则,向前移动p
else
p = q;
}
}
}
// 更新头节点
final void updateHead(Node<E> h, Node<E> p) {
if (h != p && casHead(h, p))
h.lazySetNext(h); // 将旧的头节点标记为已删除
}

源码分析

  1. 使用双层循环,外层循环用于处理需要重新从头开始的情况
  2. 从头节点开始查找第一个有效节点(item不为null)
  3. 如果找到有效节点p:
    • 使用CAS操作尝试将p的item设为null,标记元素已移除
    • 如果成功,可能还需要更新head引用(但这不是必须的,体现了松弛不变量)
    • 返回移除的元素
  4. 如果p的next为null,说明队列为空,返回null
  5. 如果p的next指向自己,说明p已被删除,需要重新从头开始
  6. 如果p不是有效节点,向前移动p继续查找

updateHead方法用于更新头节点,它不仅更新head引用,还将旧的头节点标记为已删除(next指向自身)。这种标记方式使得其他线程能够识别并跳过已删除的节点。

poll方法的实现同样体现了ConcurrentLinkedQueue的关键设计思想:

  • 使用CAS操作保证线程安全
  • 松弛不变量设计(head不一定指向第一个有效节点)
  • 惰性删除(先将item设为null,延迟物理删除)
  • 处理并发修改和已删除节点的情况

peek方法用于返回队列头部的元素但不移除它,是一个只读操作:

public E peek() {
restartFromHead:
for (;;) {
// 从头节点开始
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
// 如果找到有效节点或队列为空,更新head并返回
if (item != null || (q = p.next) == null) {
updateHead(h, p);
return item;
}
// 如果p的next指向自己,说明p已被删除,需要从头开始
else if (p == q)
continue restartFromHead;
// 否则,向前移动p
else
p = q;
}
}
}

源码分析

  1. 与poll方法类似,使用双层循环从头节点开始查找
  2. 查找第一个有效节点(item不为null)
  3. 如果找到有效节点,更新head引用并返回item(但不修改item)
  4. 如果队列为空(next为null),同样更新head引用并返回null
  5. 处理已删除节点的情况(next指向自身)

peek方法虽然是只读操作,但它仍然可能更新head引用,这是一种优化,可以帮助跳过已删除的节点,减少后续操作的遍历步骤。

size方法用于计算队列中的元素数量,在ConcurrentLinkedQueue中是一个O(n)操作:

public int size() {
int count = 0;
for (Node<E> p = first(); p != null; p = succ(p))
if (p.item != null)
// 最多统计Integer.MAX_VALUE个元素
if (++count == Integer.MAX_VALUE)
break;
return count;
}
// 获取第一个节点
Node<E> first() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
boolean hasItem = (p.item != null);
if (hasItem || (q = p.next) == null) {
updateHead(h, p);
return hasItem ? p : null;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
// 获取后继节点
Node<E> succ(Node<E> p) {
Node<E> next = p.next;
return (p == next) ? head : next;
}

源码分析

  1. size方法通过遍历整个队列来计算元素数量
  2. 从第一个有效节点开始,依次访问每个节点
  3. 只统计item不为null的节点
  4. 为了防止无限循环,最多统计Integer.MAX_VALUE个元素
  5. first()方法用于获取第一个有效节点,类似于peek的实现
  6. succ()方法用于获取节点的后继,处理已删除节点的特殊情况

size方法的时间复杂度为O(n),这是ConcurrentLinkedQueue的一个重要限制。在高并发环境下,size方法不仅性能较差,而且返回的结果可能不准确,因为在遍历过程中队列可能被并发修改。因此,应避免频繁调用size方法,特别是在大型队列上。

ConcurrentLinkedQueue的各操作时间复杂度如下:

操作 时间复杂度 说明
offer(E e) O(1) 在尾部添加元素,通常是常数时间
poll() O(1) 从头部移除元素,通常是常数时间
peek() O(1) 查看头部元素,通常是常数时间
isEmpty() O(1) 检查队列是否为空,常数时间
size() O(n) 需要遍历整个队列计算大小
contains(Object o) O(n) 需要遍历队列查找元素
remove(Object o) O(n) 需要遍历队列查找并移除元素
iterator() O(1) 创建迭代器,常数时间
迭代操作 O(n) 遍历n个元素需要线性时间

从时间复杂度来看,ConcurrentLinkedQueue的核心操作(offer、poll、peek)都是O(1)的,这使得它在高频率的入队出队操作中表现优异。但需要注意的是,由于松弛不变量的设计,实际操作可能需要额外的遍历步骤,特别是在高并发环境下。

size()和contains()等需要遍历队列的操作是O(n)的,应当谨慎使用,特别是在大型队列上。

ConcurrentLinkedQueue的并发性能特点:

  1. 无锁设计

    • 使用CAS操作代替传统锁,避免了线程阻塞和上下文切换
    • 在高并发环境下,无锁设计可以显著减少线程调度开销
    • 特别适合短暂操作和高频访问场景
  2. 读写性能

    • 入队和出队操作都是无锁的,多线程可以同时进行
    • 在高并发下,性能随线程数增加而平滑扩展,不会出现锁竞争导致的性能断崖
    • 相比基于锁的队列实现,在高并发下通常有更好的吞吐量
  3. CAS冲突

    • 高并发下可能出现CAS操作冲突,导致重试
    • 冲突概率随并发度增加而增加,但影响通常比锁竞争小
    • 在极高并发下,可能出现“活锁”现象(线程不断重试但总是失败)
  4. 内存影响

    • 惰性删除策略可能导致已删除节点暂时保留在队列中
    • 在高频率的入队出队操作下,可能增加内存占用和GC压力
    • 但总体上内存效率优于某些需要额外同步结构的并发队列

总体来说,ConcurrentLinkedQueue在并发性能方面表现优异,特别是在读多写多且操作短暂的场景下。它的无锁设计使得性能随并发度增加而平滑扩展,避免了传统锁实现在高并发下的性能断崖。

适用场景

  1. 高并发、短操作场景:当有多个线程频繁进行短暂的入队出队操作时,ConcurrentLinkedQueue的无锁设计可以显著提高吞吐量。
  2. 生产消费速率匹配的场景:当生产者和消费者的速率基本匹配,不需要通过阻塞来调节速率时,非阻塞队列更为高效。
  3. 不希望线程阻塞的场景:如实时系统、UI线程等,需要保持响应性,不能容忍线程长时间阻塞。
  4. 事件处理系统:用于在多个线程间传递事件,每个事件处理时间短暂,不需要流量控制。
  5. 缓冲区实现:作为临时存储区,多个生产者向其中写入数据,多个消费者从中读取数据。

不适用场景

  1. 需要阻塞等待的场景:如果消费者需要等待队列中有元素可用,或生产者需要等待队列有空间,应使用BlockingQueue。
  2. 需要容量限制的场景:ConcurrentLinkedQueue是无界队列,如果需要限制队列大小,应使用有界队列如ArrayBlockingQueue。
  3. 需要精确大小计数的场景:size()方法需要O(n)时间,且在并发环境下可能不准确。
  4. 生产消费速率不匹配的场景:如果生产者速率远快于消费者,使用无界队列可能导致内存溢出;如果消费者速率快于生产者,使用阻塞队列可以避免消费者空转。
  5. 需要有序性保证的场景:虽然ConcurrentLinkedQueue是FIFO队列,但在高并发下,入队和出队的顺序可能不完全符合线程执行顺序。
  1. 简单的生产者-消费者模式

    private final ConcurrentLinkedQueue<Task> taskQueue = new ConcurrentLinkedQueue<>();
    // 生产者线程
    public void produce(Task task) {
    taskQueue.offer(task);
    }
    // 消费者线程
    public void consume() {
    while (true) {
    Task task = taskQueue.poll();
    if (task != null) {
    processTask(task);
    } else {
    // 队列为空,可以选择等待一段时间再尝试
    Thread.sleep(100);
    }
    }
    }
  2. 事件分发系统

    private final ConcurrentLinkedQueue<Event> eventQueue = new ConcurrentLinkedQueue<>();
    private final ExecutorService executorService = Executors.newFixedThreadPool(4);
    // 提交事件
    public void submitEvent(Event event) {
    eventQueue.offer(event);
    }
    // 启动事件处理
    public void startEventProcessing() {
    for (int i = 0; i < 4; i++) {
    executorService.submit(() -> {
    while (!Thread.currentThread().isInterrupted()) {
    Event event = eventQueue.poll();
    if (event != null) {
    processEvent(event);
    } else {
    Thread.yield(); // 让出CPU时间片
    }
    }
    });
    }
    }
  3. 工作窃取模式

    class WorkStealingPool {
    private final int nThreads;
    private final ConcurrentLinkedQueue<Task>[] queues;
    private final Thread[] threads;
    @SuppressWarnings("unchecked")
    public WorkStealingPool(int nThreads) {
    this.nThreads = nThreads;
    this.queues = new ConcurrentLinkedQueue[nThreads];
    this.threads = new Thread[nThreads];
    for (int i = 0; i < nThreads; i++) {
    queues[i] = new ConcurrentLinkedQueue<>();
    final int index = i;
    threads[i] = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
    Task task = queues[index].poll();
    if (task == null) {
    // 尝试从其他队列窃取任务
    task = stealTask();
    }
    if (task != null) {
    executeTask(task);
    } else {
    Thread.yield();
    }
    }
    });
    threads[i].start();
    }
    }
    public void submit(Task task, int queueIndex) {
    queues[queueIndex % nThreads].offer(task);
    }
    private Task stealTask() {
    for (int i = 0; i < nThreads; i++) {
    Task task = queues[i].poll();
    if (task != null) {
    return task;
    }
    }
    return null;
    }
    }
  4. 批处理模式

    private final ConcurrentLinkedQueue<Request> requestQueue = new ConcurrentLinkedQueue<>();
    // 提交请求
    public void submitRequest(Request request) {
    requestQueue.offer(request);
    }
    // 批量处理
    public void processBatch(int batchSize) {
    List<Request> batch = new ArrayList<>(batchSize);
    Request request;
    while (batch.size() < batchSize && (request = requestQueue.poll()) != null) {
    batch.add(request);
    }
    if (!batch.isEmpty()) {
    processBatchRequests(batch);
    }
    }
  1. size()方法的性能

    • 避免频繁调用size()方法,特别是在大型队列上
    • size()方法需要O(n)时间,且在并发环境下可能不准确
    • 如果需要知道队列是否为空,使用isEmpty()方法而非size() == 0
  2. 无界队列的内存风险

    • ConcurrentLinkedQueue是无界队列,理论上可以无限增长
    • 如果生产速率持续快于消费速率,可能导致内存溢出
    • 考虑实现自定义的背压机制或监控队列大小
  3. 轮询开销

    • 由于是非阻塞队列,消费者在队列为空时通常需要轮询
    • 简单的忙等待会浪费CPU资源
    • 考虑使用Thread.sleep()、Thread.yield()或其他等待策略
  4. 元素可见性

    • 虽然队列操作是线程安全的,但队列元素本身的可见性需要单独保证
    • 确保元素是不可变的,或者使用适当的同步机制保护可变元素
  5. 迭代器弱一致性

    • 迭代器提供弱一致性保证,可能看不到迭代过程中的并发修改
    • 迭代器不会抛出ConcurrentModificationException,但可能跳过或重复元素
    • 如果需要强一致性视图,考虑使用额外的同步措施
  6. 空元素限制

    • ConcurrentLinkedQueue不允许插入null元素
    • 尝试插入null会抛出NullPointerException
    • 这是一个重要的设计决策,因为poll()方法返回null表示队列为空
  7. 性能监控

    • 在生产环境中监控ConcurrentLinkedQueue的性能
    • 关注队列大小、GC活动和CPU使用率
    • 如果发现性能问题,考虑调整并发参数或使用其他数据结构

ConcurrentLinkedQueue是Java并发包中提供的一个线程安全的无界非阻塞队列,基于链表数据结构实现。它通过无锁算法保证了高并发环境下的安全访问,特别适合读写频繁且不希望线程阻塞的场景。

核心特点

  • 非阻塞队列:操作永不阻塞线程,即使队列为空或已满
  • 无界队列:理论上可以无限扩展,只受系统内存限制
  • 线程安全:基于CAS等非阻塞算法实现线程安全
  • 高并发性能:在高并发环境下性能优于使用锁的队列实现
  • O(1)时间复杂度的入队和出队操作
  • O(n)时间复杂度的size()操作

设计思想

  • 无锁并发控制:通过CAS等无锁算法避免传统锁的性能瓶颈
  • 松弛不变量:放宽对头尾节点的严格要求,减少CAS操作次数
  • 惰性删除:先标记节点,延迟物理删除,减少内存分配和GC压力
  • 非阻塞设计:操作永不阻塞,提高系统响应性和吞吐量

ConcurrentLinkedQueue的设计体现了现代并发编程的重要思想:通过精心设计的数据结构和算法,可以在不使用传统锁的情况下实现高效的并发控制。它特别适合高并发、短操作场景,如事件处理系统、消息传递系统、任务分发系统等。

理解ConcurrentLinkedQueue的工作原理和适用场景,可以帮助我们在合适的地方使用它,充分发挥其优势,同时避免其局限性带来的问题。