当前位置: 首页 > news >正文

JCTools Spsc:单生产者-单消费者无锁队列

SpscArrayQueue

SpscArrayQueue 是 JCTools 中性能最高的有界队列之一,专为 单生产者-单消费者(Single-Producer-Single-Consumer) 场景设计。它的实现是无锁(Lock-Free)并且是无等待(Wait-Free)的,这意味着任何线程的操作都可以在有限的步骤内完成,不会因为其他线程的挂起或延迟而无限等待。

我们将从继承体系与内存布局构造函数核心方法offer核心方法poll 以及其他关键方法几个方面来逐一剖析。

SpscArrayQueue 的性能秘诀很大程度上隐藏在其精巧的继承结构中。这个结构并非为了代码复用,而是为了精确控制对象在内存中的字段布局,从而最大化地利用 CPU 缓存并消除伪共享(False Sharing)。

让我们自顶向下梳理这个继承链:

  1. ConcurrentCircularArrayQueue<E>

    • 职责:提供了所有循环数组队列的基础设施。
    • 核心字段final E[] buffer (存储元素的数组), final long mask (用于快速计算数组索引的掩码)。
  2. SpscArrayQueueColdField<E>

    • 职责:定义“冷”字段(Cold Field),即初始化后很少或不再改变的字段。
    • 核心字段final int lookAheadStep。这是“前瞻优化”的关键参数,在构造时计算一次后便不再改变。将它放在继承链的早期,是为了让它远离后面频繁变化的“热”字段。
  3. SpscArrayQueueL1Pad<E>

    • 职责一级缓存行填充(L1 Padding)
    • 核心字段byte b000, b001, ... 等大量无用字节。
    • 目的:它的唯一作用就是在 lookAheadStep 和即将定义的生产者索引之间填充足够的字节(通常是128字节),确保它们位于不同的 CPU 缓存行中。
  4. SpscArrayQueueProducerIndexFields<E>

    • 职责:定义生产者所需的所有索引字段。
    • 核心字段
      • private volatile long producerIndex生产者索引volatile修饰,确保其对消费者线程的可见性。这是生产者侧的热点字段
      • protected long producerLimit生产者限制索引。这是一个普通的 long,作为消费者进度的本地缓存,是“前瞻优化”的核心。它只被生产者线程访问,因此无需 volatile
  5. SpscArrayQueueL2Pad<E>

    • 职责二级缓存行填充(L2 Padding)
    • 核心字段:同样是大量无用字节。
    • 目的:这是最关键的填充。它在生产者索引(producerIndex)和即将定义的消费者索引(consumerIndex)之间制造了巨大的鸿沟,从物理上隔离了生产者和消费者线程各自的热点字段,彻底根除了伪共享。
  6. SpscArrayQueueConsumerIndexField<E>

    • 职责:定义消费者所需的核心索引字段。
    • 核心字段private volatile long consumerIndex消费者索引volatile修饰,确保其对生产者线程的可见性。这是消费者侧的热点字段
  7. SpscArrayQueueL3Pad<E>

    • 职责三级缓存行填充(L3 Padding)
    • 目的:在消费者索引之后再次进行填充,确保 SpscArrayQueue 自身可能添加的任何字段都与消费者索引隔离。
  8. SpscArrayQueue<E>

    • 职责:最终的实现类,提供了 offerpoll 等具体方法的逻辑。

小结:这个看似复杂的继承链,实际上是一个精密的内存布局蓝图,其核心思想就是用空间(Padding)换时间(避免缓存伪共享)


构造函数分析

// ... existing code ...
public class SpscArrayQueue<E> extends SpscArrayQueueL3Pad<E>
{public SpscArrayQueue(final int capacity){super(Math.max(capacity, 4));}
// ... existing code ...
  • super(Math.max(capacity, 4));
    • 构造函数将容量的最小值强制设为 4。
    • 原因:这与“前瞻优化”有关。lookAheadStep 通常被计算为 capacity / 4。如果容量小于4,lookAheadStep 就会是0,导致优化失效。因此,最小容量为4可以保证 lookAheadStep 至少为1,让优化机制能够正常工作。

offer :前瞻优化的艺术

offer 方法是 SpscArrayQueue 性能王冠上的明珠。

// ... existing code ...@Overridepublic boolean offer(final E e){if (null == e){throw new NullPointerException();}// 1. 将字段加载到本地变量,避免重复访问final E[] buffer = this.buffer;final long mask = this.mask;final long producerIndex = this.lpProducerIndex(); // 2. 使用 lp (load plain)// 3. 快车道(Fast Path)判断if (producerIndex >= producerLimit &&!offerSlowPath(buffer, mask, producerIndex)) // 4. 慢车道(Slow Path){return false;}final long offset = calcCircularRefElementOffset(producerIndex, mask);// 5. 写入元素和索引soRefElement(buffer, offset, e); // store orderedsoProducerIndex(producerIndex + 1); // store orderedreturn true;}private boolean offerSlowPath(final E[] buffer, final long mask, final long producerIndex){final int lookAheadStep = this.lookAheadStep;// 6. 前瞻检查if (null == lvRefElement(buffer,calcCircularRefElementOffset(producerIndex + lookAheadStep, mask))){// 7. 更新 producerLimitproducerLimit = producerIndex + lookAheadStep;}else{// 8. 精确检查final long offset = calcCircularRefElementOffset(producerIndex, mask);if (null != lvRefElement(buffer, offset)){return false;}}return true;}
// ... existing code ...
  1. 本地变量:将 buffermaskproducerIndex 加载到栈上,避免在方法内部重复从内存(堆)中读取,提高效率。
  2. lpProducerIndex()lp 代表 load plain。生产者线程自身访问自己的索引,不需要 volatile 读的内存屏障开销,直接用最快的普通读。
  3. 快车道producerIndex >= producerLimit 是核心判断。只要生产者索引还没达到缓存的消费者进度限制,就认为队列有空位,直接向下执行,这是绝大多数情况下offer的执行路径。
  4. 慢车道:当快车道条件不满足时,进入 offerSlowPath。这表示生产者需要更新它对消费者进度的认知了。
  5. soRefElement / soProducerIndexso 代表 store ordered。这是一个轻量级的写操作,它保证了“先写元素,再更新索引”这个顺序不会被指令重排打乱。这对于消费者能正确看到数据至关重要。
  6. 前瞻检查:这是慢车道的精髓。它不直接去读取 volatile 的 consumerIndex,而是去“偷看”未来 lookAheadStep 步之后的位置上的元素是否为 null。它使用 lvRefElement (load volatile) 来确保能看到消费者线程将该位置设为 null 的操作。
  7. 更新 producerLimit:如果“偷看”的位置是 null,说明消费者已经走得很远了。生产者就可以大胆地将自己的 producerLimit 向前推进 lookAheadStep 步,然后返回快车道继续高速生产。
  8. 精确检查:如果“偷看”的位置不是 null,说明队列可能满了。此时,才需要做一次精确检查,即检查当前 producerIndex 指向的位置是否为空。如果当前位置也不为空,则队列确实满了,offer 失败。

小结offer 方法通过 producerLimit 缓存和“前瞻”机制,将昂贵的 volatile 读操作(检查消费者进度)的频率降到最低,使得生产者在绝大部分时间里都能在无跨线程同步的“快车道”上运行。


poll() 

相比 offerpoll 的逻辑要简单得多,因为它不需要做任何优化,只需直接与生产者“交接”即可。

// ... existing code ...@Overridepublic E poll(){final long consumerIndex = this.lpConsumerIndex(); // 1. lp (load plain)final long offset = calcCircularRefElementOffset(consumerIndex, mask);final E[] buffer = this.buffer;final E e = lvRefElement(buffer, offset); // 2. lv (load volatile)if (null == e){return null;}// 3. 清理元素和更新索引soRefElement(buffer, offset, null); // store orderedsoConsumerIndex(consumerIndex + 1); // store orderedreturn e;}
// ... existing code ...
  1. lpConsumerIndex():消费者读取自己的索引,同样使用最高效的普通读。
  2. lvRefElement():这是 poll 方法的核心。它必须使用 volatile 读 (load volatile) 来读取数组元素,以确保能够看到生产者线程通过 soRefElement 写入的最新数据。这是生产者和消费者之间数据传递的“握手点”。
  3. soRefElement / soConsumerIndex:与 offer 类似,使用 store ordered 保证“先将槽位置为 null,再更新消费者索引”的顺序。将槽位置为 null 对于 offer 方法的“前瞻”检查至关重要。

drain vs. poll 循环

drain 的目标是从队列中批量取出元素。

如果只是简单地循环调用 poll()

// 伪代码:一个简单但低效的 drain 实现
for (int i = 0; i < limit; i++) {E e = poll();if (e == null) {return i;}c.accept(e);
}

这样做的问题在于:

  • 方法调用开销:每次循环都会有一次 poll() 方法的调用开销。
  • 重复的字段加载poll() 方法内部每次都会去加载 consumerIndexmaskbuffer 等字段。
  • 逻辑冗余poll() 内部的 null 判断会在循环中重复执行。

SpscArrayQueue 的实际 drain 实现: 它将 poll 的核心逻辑 内联(inline) 并展开到了 drain 的循环体内部。

// ... existing code ...@Overridepublic int drain(final Consumer<E> c, final int limit){// ... 参数检查 ...// 1. 一次性加载所有需要的字段到本地变量final E[] buffer = this.buffer;final long mask = this.mask;final long consumerIndex = this.lpConsumerIndex();// 2. 直接在循环中操作for (int i = 0; i < limit; i++){final long index = consumerIndex + i;final long offset = calcCircularRefElementOffset(index, mask);// 3. 直接从 buffer 中 volatile 读取元素,绕过 poll()final E e = lvRefElement(buffer, offset);if (null == e){return i;}// 4. 直接操作 buffer 和索引,绕过 poll()soRefElement(buffer, offset, null);soConsumerIndex(index + 1); // ordered store -> atomic and ordered for size()c.accept(e);}return limit;}
// ... existing code ...

优化点分析

  1. 避免方法调用:完全没有 poll() 的调用,消除了相关开销。
  2. 字段一次性加载:在循环开始前将 buffermaskconsumerIndex 加载到栈上,循环体内直接使用,速度更快。
  3. 逻辑内联poll() 的核心逻辑——计算偏移量、读取元素、判断null、设置null、更新索引——被直接实现在了 drain 的循环中,没有任何冗余。

所以,drain 是一个高度优化的 poll 批量版本,而不是它的简单循环。

fill vs. offer 循环

fill 的优化更为精妙,因为它必须处理 SpscArrayQueue 最核心的 前瞻(Look-Ahead) 机制。

如果只是简单地循环调用 offer()

// 伪代码:一个简单但性能很差的 fill 实现
for (int i = 0; i < limit; i++) {if (!offer(s.get())) {return i;}
}

这样做的问题非常严重:

  • 破坏前瞻优化:每次调用 offer 都会进行一次 producerIndex >= producerLimit 的检查。如果队列接近满,可能会频繁进入 offerSlowPath,导致性能急剧下降。
  • 高昂的慢路径成本:在慢路径中,每次循环都会进行一次 lvRefElement 的 volatile 读,这本是应该尽量避免的。

SpscArrayQueue 的实际 fill 实现: 它将前瞻机制也应用到了批量操作中,实现了批量版本的快、慢路径。

// ... existing code ...@Overridepublic int fill(final Supplier<E> s, final int limit){// ... 参数检查 ...final E[] buffer = this.buffer;final long mask = this.mask;final int lookAheadStep = this.lookAheadStep;final long producerIndex = this.lpProducerIndex();for (int i = 0; i < limit; i++){final long index = producerIndex + i;// 1. 批量快路径:检查前方 `lookAheadStep` 距离的位置final long lookAheadElementOffset =calcCircularRefElementOffset(index + lookAheadStep, mask);if (null == lvRefElement(buffer, lookAheadElementOffset)){// 2. 如果安全,一次性写入 lookAheadStep 个元素int lookAheadLimit = Math.min(lookAheadStep, limit - i);for (int j = 0; j < lookAheadLimit; j++){final long offset = calcCircularRefElementOffset(index + j, mask);soRefElement(buffer, offset, s.get());soProducerIndex(index + j + 1);}// 3. 外层循环步进,跳过已经填充的元素i += lookAheadLimit - 1;}else // 4. 批量慢路径:如果前方不安全,则回退到一次检查一个{final long offset = calcCircularRefElementOffset(index, mask);if (null != lvRefElement(buffer, offset)){return i; // 队列已满}soRefElement(buffer, offset, s.get());soProducerIndex(index + 1);}}return limit;}
// ... existing code ...

优化点分析

  1. 批量前瞻fill 的循环不是一次检查一个槽位,而是一次“向前看”lookAheadStep 个槽位。
  2. 批量快路径:如果前瞻检查通过,它就获得了连续写入 lookAheadStep 个元素的“许可”,然后在一个内部循环里快速完成填充,极大地提高了效率。
  3. 成本分摊:昂贵的 volatile 读(lvRefElement)的成本被分摊到了 lookAheadStep 个元素上,而不是每个元素都承担一次。
  4. 批量慢路径:只有当快路径走不通时,才退化为逐个检查的慢路径,逻辑与单次 offer 的慢路径类似。

fill 和 drain 体现了 JCTools 的设计哲学:针对不同的使用场景(单个操作 vs 批量操作)提供专门优化的路径,以压榨出极致的性能。


总结

SpscArrayQueue 是一个将计算机体系结构、Java内存模型和算法设计完美结合的典范。它的卓越性能源于以下几个关键设计:

  • 精密的内存布局:通过多层继承和缓存行填充,彻底消除了伪共享。
  • 前瞻性优化:在生产者侧引入 producerLimit 缓存,实现了“快车道”和“慢车道”逻辑,极大降低了跨线程 volatile 读的频率。
  • 细粒度的内存屏障:充分利用 Unsafe 提供的 plainorderedvolatile 等不同强度的内存操作,在保证并发正确性的前提下,将性能压榨到极致。

因此,SpscArrayQueue 成为了单生产者单消费者场景下近乎完美的队列解决方案。

SpscArrayQueueColdField

这个抽象类是 SpscArrayQueue 继承链中的一个重要组成部分

  • SpscArrayQueue...:这是为 SPSC (Single-Producer, Single-Consumer) 单生产者单消费者数组队列服务的基类。
  • ...ColdField: "Cold Field"(冷字段)是 JCTools 中的一个术语,特指那些在对象构建后值不再改变或者极少改变的字段。将这些字段聚合在一起,可以提高 CPU 缓存的效率。因为这些字段所在的缓存行(Cache Line)一旦被加载,就很少会因为写操作而失效,从而减少了不必要的缓存同步开销。在这个类中,lookAheadStep 就是一个典型的“冷字段”。
  • extends ConcurrentCircularArrayQueue<E>: 它继承自 ConcurrentCircularArrayQueue,这意味着它复用了 JCTools 中通用的循环数组队列的基础设施,例如 buffer 数组、mask 掩码以及容量计算等。

这个类的核心是定义了 final int lookAheadStep; 字段,并于构造函数中对其进行初始化。

这个字段是 SpscArrayQueue 实现高性能的关键优化——“前瞻(Look-Ahead)”——的基础。

abstract class SpscArrayQueueColdField<E> extends ConcurrentCircularArrayQueue<E>
{final int lookAheadStep;SpscArrayQueueColdField(int capacity){super(capacity);int actualCapacity = capacity();lookAheadStep = SpscLookAheadUtil.computeLookAheadStep(actualCapacity);}}

为什么需要前瞻优化?

在生产者-消费者模型中,生产者需要知道队列是否已满,才能决定能否继续添加元素。最直接的判断方式是比较生产者索引 pIndex 和消费者索引 cIndex。但在并发环境中,pIndex 由生产者线程更新,cIndex 由消费者线程更新,这意味着生产者要读取一个由其他线程写入的 volatile 变量 cIndex,这个操作相对耗时。

SpscArrayQueue 通过“前瞻”机制,巧妙地避免了在每次 offer 操作时都去读取 cIndex

前瞻优化如何工作?

  1. 缓存消费者进度: 生产者维护一个本地的 producerLimit 字段,作为对消费者进度的缓存。只要 pIndex < producerLimit,生产者就认为队列有空间,可以非常快速地执行 offer(这被称为“快车道”)。

  2. 触发“慢车道”: 当 pIndex 追上 producerLimit 时,生产者不能再假设队列有空间。此时触发“慢车道”逻辑 (offerSlowPath 方法)。

  3. 执行“前瞻”: 在慢车道中,生产者并不直接去读取 cIndex,而是“向前看” lookAheadStep 个距离。它会检查 buffer[pIndex + lookAheadStep] 这个位置的元素是否为 null

    • 如果为 null: 这说明消费者至少已经消费到了这个位置,证明队列中至少还有 lookAheadStep 个空位。此时,生产者就可以安全地将自己的 producerLimit 更新为 pIndex + lookAheadStep,然后返回“快车道”继续快速 offer
    • 如果不为 null: 说明队列可能真的满了,此时才需要进行更精确的检查(检查 buffer[pIndex] 是否为空)。
  4. lookAheadStep 的计算: 这个“步长”由 SpscLookAheadUtil.computeLookAheadStep(actualCapacity) 计算得出,通常是容量的四分之一。这是一个权衡:步长越大,触发慢车道的频率越低,但可能导致队列在未完全占满时就提前认为已满;步长越小,判断越精确,但触发慢车道的频率会更高。

总结

SpscArrayQueueColdField 的作用是:

  1. 作为 SpscArrayQueue 继承链的一部分,通过“冷字段”的组织方式优化内存布局。
  2. 引入并初始化 lookAheadStep 字段,为 SpscArrayQueue 的核心性能优化——前瞻机制——奠定基础。

这个机制使得 SpscArrayQueue 在大多数情况下,其 offer 操作都无需进行跨线程的 volatile 读,从而实现了极高的吞吐量,是无锁队列设计中的一个经典优化。

SpscArrayQueueProducerIndexFields

这个抽象类专门负责管理生产者侧的索引

  • ...ProducerIndexFields: 名称后缀清晰地指出了它的职责——定义和管理与生产者索引(Producer Index)相关的字段。
  • extends SpscArrayQueueL1Pad<E>: 它继承自 SpscArrayQueueL1Pad。这意味着它位于 lookAheadStep 字段和生产者索引之间,插入了一段缓存行填充(Padding)。这个 L1Pad 的作用是将 lookAheadStep 这个“冷”字段与 producerIndex 这个“热”字段分离开,避免它们位于同一个缓存行而引发不必要的缓存失效。

这个类定义了两个关键的生产者端字段:

// ... existing code ...
// $gen:ordered-fields
abstract class SpscArrayQueueProducerIndexFields<E> extends SpscArrayQueueL1Pad<E>
{private final static long P_INDEX_OFFSET = fieldOffset(SpscArrayQueueProducerIndexFields.class, "producerIndex");private volatile long producerIndex;protected long producerLimit;SpscArrayQueueProducerIndexFields(int capacity)
// ... existing code ...
  1. private volatile long producerIndex;

    • 这是真正的生产者索引。它记录了生产者下一个将要写入数据的位置。
    • 它被声明为 volatile,这是至关重要的。因为 consumerIndex(由消费者线程更新)需要读取这个值来判断队列是否为空。volatile 保证了生产者对 producerIndex 的写入对消费者线程是立即可见的。
    • 这个字段是**“热”字段**,因为生产者线程会频繁地更新它。
  2. protected long producerLimit;

    • 这是生产者侧对消费者进度的缓存,是实现“前瞻(Look-Ahead)”优化的关键。
    • 不是 volatile 的。这是一个非常重要的性能优化点。因为这个字段只由生产者线程自身读写,它不需要保证跨线程的可见性。生产者线程可以非常快速地访问这个普通的 long 类型字段。
    • 它的值代表了生产者在不检查消费者真实进度的情况下,可以安全生产到的索引上限。当 producerIndex 达到 producerLimit 时,才会触发“慢车道”逻辑去更新这个 producerLimit

这个类还提供了一套访问和更新 producerIndex 的方法,这些方法都利用了 sun.misc.Unsafe 来实现最高效的内存操作。

// ... existing code ...@Overridepublic final long lvProducerIndex(){return producerIndex;}final long lpProducerIndex(){return UNSAFE.getLong(this, P_INDEX_OFFSET);}final void soProducerIndex(final long newValue){UNSAFE.putOrderedLong(this, P_INDEX_OFFSET, newValue);}
// ... existing code ...
  • lvProducerIndex()lv 是 load volatile 的缩写。它提供了对 producerIndex 的 volatile 读。这通常是给消费者线程使用的,以获取生产者最新的进度。
  • lpProducerIndex()lp 是 load plain 的缩写。它提供了对 producerIndex 的普通(非 volatile)读。这只在生产者线程内部使用,因为生产者自己知道自己的进度,不需要 volatile 语义带来的额外开销。
  • soProducerIndex(final long newValue)so 是 store ordered 的缩写。它使用 putOrderedLong 来更新 producerIndex。这是一个比 putVolatileLong 更轻量级的写操作。它保证了在它之前的内存写入操作不会被重排序到它之后,并且最终会将新值刷新到主存,但它不保证其他线程能立即看到这个新值(即它是一个“延迟写”)。这对于 SPSC 场景是足够的,因为只有消费者需要看到这个值的变化,而消费者侧的 volatile 读最终会确保看到这个更新。

总结

SpscArrayQueueProducerIndexFields 是 SpscArrayQueue 高性能设计的核心体现:

  1. 职责分离:将生产者相关的索引字段集中管理。
  2. 缓存行填充:通过继承 L1Pad,将热点字段 producerIndex 与其他字段隔离开,避免伪共享。
  3. 优化字段访问
    • 使用 volatile 的 producerIndex 确保跨线程的可见性。
    • 使用非 volatile 的 producerLimit 作为线程本地缓存,实现“快车道”入队。
  4. 精细化内存操作:利用 Unsafe 提供的 plainorderedvolatile 等不同强度的内存操作,在保证正确性的前提下,将性能压榨到极致。

SpscArrayQueueConsumerIndexField

这个类在 SpscArrayQueue 的继承结构中扮演着与 SpscArrayQueueProducerIndexFields 对称的角色,它专门负责管理消费者侧的索引

  • ...ConsumerIndexField: 后缀清晰地表明了它的核心职责——定义和管理消费者索引(Consumer Index)相关的字段和方法。
  • extends SpscArrayQueueL2Pad<E>: 它继承自 SpscArrayQueueL2Pad。这个继承关系非常关键,它在 producerIndex 相关的字段和 consumerIndex 字段之间插入了一大段缓存行填充(Padding)。L2Pad 的目的就是确保生产者线程高频访问的 producerIndex 和消费者线程高频访问的 consumerIndex 绝对不会位于同一个缓存行上。这可以彻底消除伪共享(False Sharing),是保证 SPSC 队列极致性能的基础。

这个类只定义了一个核心字段:

// ... existing code ...
//$gen:ordered-fields
abstract class SpscArrayQueueConsumerIndexField<E> extends SpscArrayQueueL2Pad<E>
{private final static long C_INDEX_OFFSET = fieldOffset(SpscArrayQueueConsumerIndexField.class, "consumerIndex");private volatile long consumerIndex;SpscArrayQueueConsumerIndexField(int capacity)
// ... existing code ...
  • private volatile long consumerIndex;
    • 这是真正的消费者索引,记录了消费者下一个将要读取数据的位置。
    • 它被声明为 volatile,这对于生产者来说是必需的。生产者需要通过读取这个值来判断队列是否已满(虽然在 SpscArrayQueue 中通过“前瞻”优化避免了频繁读取,但在“慢车道”逻辑中依然需要)。volatile 保证了消费者对 consumerIndex 的更新对生产者线程是可见的。
    • 这个字段是消费者线程侧的**“热”字段**,会被消费者线程频繁更新。

与生产者侧类似,这个类也提供了一套基于 sun.misc.Unsafe 的高效访问方法:

// ... existing code ...public final long lvConsumerIndex(){return UNSAFE.getLongVolatile(this, C_INDEX_OFFSET);}final long lpConsumerIndex(){return UNSAFE.getLong(this, C_INDEX_OFFSET);}final void soConsumerIndex(final long newValue){UNSAFE.putOrderedLong(this, C_INDEX_OFFSET, newValue);}
}
// ... existing code ...
  • lvConsumerIndex()load volatile 的缩写。它提供了对 consumerIndex 的 volatile 读。这主要是给生产者线程在“慢车道”中使用的,用以获取消费者最新的、准确的进度。
  • lpConsumerIndex()load plain 的缩写。它提供了对 consumerIndex 的普通(非 volatile)读。这只在消费者线程内部使用,因为消费者自己知道自己的进度,不需要 volatile 带来的额外内存屏障开销。
  • soConsumerIndex(final long newValue)store ordered 的缩写。它使用 putOrderedLong 来更新 consumerIndex。这是一个轻量级的“延迟写”,保证了在它之前的内存操作(比如将数组元素设为 null)不会被重排序到它之后。这对于 SPSC 场景是完全足够且高效的,因为只有生产者需要观察到这个值的变化。

总结

SpscArrayQueueConsumerIndexField 的设计哲学与 SpscArrayQueueProducerIndexFields 一脉相承,共同构成了 SpscArrayQueue 的高性能基石:

  1. 职责明确:专门管理消费者索引。
  2. 隔离热点:通过继承 L2Pad,将生产者和消费者的核心热点字段(producerIndex 和 consumerIndex)用缓存行填充进行物理隔离,从根本上杜绝了伪共享问题。
  3. 精细化内存控制:针对不同的使用场景(线程内访问 vs. 跨线程访问),提供了不同内存语义强度(plainorderedvolatile)的 Unsafe 操作方法,在保证并发正确性的前提下,最大化地提升了单线程的执行效率。

http://www.dtcms.com/a/339531.html

相关文章:

  • 使用 Map 存储值和使用对象object储存的区别
  • 18.web api 9
  • C++高频知识点(二十七)
  • three.js学习记录(第三节:平面几何体BufferGeometry)
  • ADSP-21565开发板和ADSP-21569开发板的底板设计区别
  • ComfyUI 里的 Prompt 插值器(prompt interpolation / text encoder 插值方式)的含义和作用!
  • 通信方式:命名管道
  • nuc设置脚本开机自启动
  • 9.Ansible管理大项目
  • 实现LoRa通信与低功耗流程(无SPI中断)
  • Pegasus,HBASE,Redis比较
  • UML常见图例
  • 源代码部署 LAMP 架构
  • C++小游戏NO.1游戏机
  • 通过分布式系统的视角看Kafka
  • Gemini CLI 最近版本更新说明(v0.1.17~v0.1.22)
  • pyecharts可视化图表K线图_Candlestick:从入门到精通 (进阶版)
  • 技术分享:跨域问题的由来与解决
  • AP6275S AMPAK正基WiFi6模块方案与应用
  • 阀门漏水超声波检测类产品有哪些?
  • 【habitat学习一】Habitat-Lab 配置键文档详解(CONFIG_KEYS.md)
  • 进程间通信(信号、共享内存)
  • 17.web api 8
  • STM32之beep、多文件、延迟、按键以及呼吸灯
  • 大模型部署基础设施搭建 - LlamaFactory
  • Java优选算法——滑动窗口
  • Fragment重要知识点总结
  • CloudDM 新增支持 GaussDB 与 openGauss:国产数据库管理更高效
  • OpenHarmony 之多模态输入子系统源码深度架构解析
  • Android -登录注册实践技术总结