disruptor-3.3.2源码解析(1)-序列
作者:大飞
- Disruptor中的序列-Sequence:
disruptor中较为重要的一个类是Sequence。我们设想下,在disruptor运行过程中,事件发布者(生产者)和事件处理者(消费者)在ringbuffer上相互追逐,由什么来标记它们的相对位置呢?它们根据什么从ringbuffer上发布或者处理事件呢?就是这个Sequence-序列。
我们看一下这个类的源代码,先看结构:
class LhsPadding{ protected long p1, p2, p3, p4, p5, p6, p7; } class Value extends LhsPadding{ protected volatile long value; } class RhsPadding extends Value{ protected long p9, p10, p11, p12, p13, p14, p15; } public class Sequence extends RhsPadding{ static final long INITIAL_VALUE = -1L; private static final Unsafe UNSAFE; private static final long VALUE_OFFSET; static{ UNSAFE = Util.getUnsafe(); try{ VALUE_OFFSET = UNSAFE.objectFieldOffset(Value.class.getDeclaredField("value")); }catch (final Exception e){ throw new RuntimeException(e); } } /** * 默认初始值为-1 */ public Sequence(){ this(INITIAL_VALUE); } public Sequence(final long initialValue){ UNSAFE.putOrderedLong(this, VALUE_OFFSET, initialValue); }
我们可以注意到两点:
1.通过Sequence的一系列的继承关系可以看到,它真正的用来计数的域是value,在value的前后各有7个long型的填充值,这些值在这里的作用是做cpu cache line填充,防止发生伪共享。
2.value域本身由volatile修饰,而且又看到了Unsafe类,大概猜到是要做原子操作了。
public long get(){ return value; } /** * ordered write * 在当前写操作和任意之前的读操作之间加入Store/Store屏障 */ public void set(final long value){ UNSAFE.putOrderedLong(this, VALUE_OFFSET, value); } /** * volatile write * 在当前写操作和任意之前的读操作之间加入Store/Store屏障 * 在当前写操作和任意之后的读操作之间加入Store/Load屏障 */ public void setVolatile(final long value){ UNSAFE.putLongVolatile(this, VALUE_OFFSET, value); } public boolean compareAndSet(final long expectedValue, final long newValue){ return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expectedValue, newValue); } public long incrementAndGet(){ return addAndGet(1L); } public long addAndGet(final long increment){ long currentValue; long newValue; do{ currentValue = get(); newValue = currentValue + increment; }while (!compareAndSet(currentValue, newValue)); return newValue; }
可见Sequence是一个"原子"的序列。
总结一下:Sequence是一个做了缓存行填充优化的原子序列。
public final class FixedSequenceGroup extends Sequence{ private final Sequence[] sequences; public FixedSequenceGroup(Sequence[] sequences){ this.sequences = Arrays.copyOf(sequences, sequences.length); } @Override public long get(){ return Util.getMinimumSequence(sequences); } @Override public void set(long value){ throw new UnsupportedOperationException(); } ... }FixedSequenceGroup相当于包含了若干序列的一个包装类,尽管本身继承了Sequence,但只是重写了get方法,获取内部序列组中最小的序列值,但其他的"写"方法都不支持。
- 上面看了序列的内容,框架中也针对序列的使用,提供了专门的功能接口Sequencer:
Sequencer接口扩展了Cursored和Sequenced。先看下这两个货:
public interface Cursored{ long getCursor(); }
Cursored接口只提供了一个获取当前序列值(游标)的方法。
public interface Sequenced{ /** * 数据结构中事件槽的个数(就是RingBuffer的容量) */ int getBufferSize(); /** * 判断是否还有给定的可用容量。 */ boolean hasAvailableCapacity(final int requiredCapacity); /** * 获取剩余容量。 */ long remainingCapacity(); /** * 申请下一个序列值,用来发布事件。 */ long next(); /** * 申请下N个序列值,用来发布事件。 */ long next(int n); /** * 尝试申请下一个序列值用来发布事件,这个是无阻塞的方法。 */ long tryNext() throws InsufficientCapacityException; /** * 尝试申请下N个序列值用来发布事件,这个是无阻塞的方法。 */ long tryNext(int n) throws InsufficientCapacityException; /** * 在给定的序列值上发布事件,当填充好事件后会调用这个方法。 */ void publish(long sequence); /** * 在给定的序列返回上发布事件,当填充好事件后会调用这个方法。 */ void publish(long lo, long hi); }最后看下Sequencer接口:
public interface Sequencer extends Cursored, Sequenced{ /** 序列初始值 */ long INITIAL_CURSOR_VALUE = -1L; /** * 声明一个序列,这个方法在初始化RingBuffer的时候被调用。 */ void claim(long sequence); /** * 判断一个序列是否被发布,并且发布到序列上的事件是可处理的。非阻塞方法。 */ boolean isAvailable(long sequence); /** * 添加一些追踪序列到当前实例,添加过程是原子的。 * 这些控制序列一般是其他组件的序列,当前实例可以通过这些 * 序列来查看其他组件的序列使用情况。 */ void addGatingSequences(Sequence... gatingSequences); /** * 移除控制序列。 */ boolean removeGatingSequence(Sequence sequence); /** * 基于给定的追踪序列创建一个序列栅栏,这个栅栏是提供给事件处理者 * 在判断Ringbuffer上某个事件是否能处理时使用的。 */ SequenceBarrier newBarrier(Sequence... sequencesToTrack); /** * 获取控制序列里面当前最小的序列值。 */ long getMinimumSequence(); /** * 获取RingBuffer上安全使用的最大的序列值。 * 具体实现里面,这个调用可能需要序列上从nextSequence到availableSequence之间的值。 * 如果没有比nextSequence大的可用序列,会返回nextSequence - 1。 * 为了保证正确,事件处理者应该传递一个比最后的序列值大1个单位的序列来处理。 */ long getHighestPublishedSequence(long nextSequence, long availableSequence); /* * 通过给定的数据提供者和控制序列来创建一个EventPoller */ <T> EventPoller<T> newPoller(DataProvider<T> provider, Sequence...gatingSequences); }
这里要注意一下:
Sequencer接口的很多功能是提供给事件发布者用的。
通过Sequencer可以得到一个SequenceBarrier,这货是提供给事件处理者用的。
框架中针对Sequencer接口提供了2种实现:SingleProducerSequencer和MultiProducerSequencer。
看这两个类之前,先看下它们的基类AbstractSequencer:public abstract class AbstractSequencer implements Sequencer{ private static final AtomicReferenceFieldUpdater<AbstractSequencer, Sequence[]> SEQUENCE_UPDATER = AtomicReferenceFieldUpdater.newUpdater(AbstractSequencer.class, Sequence[].class, "gatingSequences"); protected final int bufferSize; protected final WaitStrategy waitStrategy; protected final Sequence cursor = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); protected volatile Sequence[] gatingSequences = new Sequence[0]; public AbstractSequencer(int bufferSize, WaitStrategy waitStrategy){ if (bufferSize < 1){ throw new IllegalArgumentException("bufferSize must not be less than 1"); } if (Integer.bitCount(bufferSize) != 1){ throw new IllegalArgumentException("bufferSize must be a power of 2"); } this.bufferSize = bufferSize; this.waitStrategy = waitStrategy; } @Override public final long getCursor(){ return cursor.get(); } @Override public final int getBufferSize(){ return bufferSize; } @Override public final void addGatingSequences(Sequence... gatingSequences){ SequenceGroups.addSequences(this, SEQUENCE_UPDATER, this, gatingSequences); } @Override public boolean removeGatingSequence(Sequence sequence){ return SequenceGroups.removeSequence(this, SEQUENCE_UPDATER, sequence); } @Override public long getMinimumSequence(){ return Util.getMinimumSequence(gatingSequences, cursor.get()); } @Override public SequenceBarrier newBarrier(Sequence... sequencesToTrack){ return new ProcessingSequenceBarrier(this, waitStrategy, cursor, sequencesToTrack); } @Override public <T> EventPoller<T> newPoller(DataProvider<T> dataProvider, Sequence... gatingSequences){ return EventPoller.newInstance(dataProvider, this, new Sequence(), cursor, gatingSequences); } }
可见,基类基本上的作用就是管理追踪序列和关联当前序列。
bstract class SingleProducerSequencerPad extends AbstractSequencer{ protected long p1, p2, p3, p4, p5, p6, p7; public SingleProducerSequencerPad(int bufferSize, WaitStrategy waitStrategy){ super(bufferSize, waitStrategy); } } abstract class SingleProducerSequencerFields extends SingleProducerSequencerPad{ public SingleProducerSequencerFields(int bufferSize, WaitStrategy waitStrategy){ super(bufferSize, waitStrategy); } protected long nextValue = Sequence.INITIAL_VALUE; protected long cachedValue = Sequence.INITIAL_VALUE; } public final class SingleProducerSequencer extends SingleProducerSequencerFields{ protected long p1, p2, p3, p4, p5, p6, p7; public SingleProducerSequencer(int bufferSize, final WaitStrategy waitStrategy){ super(bufferSize, waitStrategy); }
又是缓存行填充,真正使用的值是nextValue和cachedValue。
@Override public boolean hasAvailableCapacity(final int requiredCapacity){ long nextValue = this.nextValue; long wrapPoint = (nextValue + requiredCapacity) - bufferSize; long cachedGatingSequence = this.cachedValue; if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue){ long minSequence = Util.getMinimumSequence(gatingSequences, nextValue); this.cachedValue = minSequence; if (wrapPoint > minSequence){ return false; } } return true; }
hasAvailableCapacity方法可以这样理解:
当前序列的nextValue + requiredCapacity是事件发布者要申请的序列值。
当前序列的cachedValue记录的是之前事件处理者申请的序列值。
想一下一个环形队列,事件发布者在什么情况下才能申请一个序列呢?
事件发布者当前的位置在事件处理者前面,并且不能从事件处理者后面追上事件处理者(因为是环形),
即 事件发布者要申请的序列值大于事件处理者之前的序列值 且 事件发布者要申请的序列值减去环的长度要小于事件处理者的序列值
如果满足这个条件,即使不知道当前事件处理者的序列值,也能确保事件发布者可以申请给定的序列。
如果不满足这个条件,就需要查看一下当前事件处理者的最小的序列值(因为可能有多个事件处理者),如果当前要申请的序列值比当前事件处理者的最小序列值大了一圈(从后面追上了),那就不能申请了(申请的话会覆盖没被消费的事件),也就是说没有可用的空间(用来发布事件)了,也就是hasAvailableCapacity方法要表达的意思。
@Override public long next(){ return next(1); } @Override public long next(int n){ if (n < 1) { throw new IllegalArgumentException("n must be > 0"); } long nextValue = this.nextValue; long nextSequence = nextValue + n; long wrapPoint = nextSequence - bufferSize; long cachedGatingSequence = this.cachedValue; if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue){ long minSequence; while (wrapPoint > (minSequence = Util.getMinimumSequence(gatingSequences, nextValue))){ LockSupport.parkNanos(1L); // TODO: Use waitStrategy to spin? } this.cachedValue = minSequence; } this.nextValue = nextSequence; return nextSequence; }next方法是真正申请序列的方法,里面的逻辑和hasAvailableCapacity一样,只是在不能申请序列的时候会阻塞等待一下,然后重试。
@Override public long tryNext() throws InsufficientCapacityException{ return tryNext(1); } @Override public long tryNext(int n) throws InsufficientCapacityException{ if (n < 1){ throw new IllegalArgumentException("n must be > 0"); } if (!hasAvailableCapacity(n)){ throw InsufficientCapacityException.INSTANCE; } long nextSequence = this.nextValue += n; return nextSequence; }tryNext方法是next方法的非阻塞版本,不能申请就抛异常。
@Override public long remainingCapacity(){ long nextValue = this.nextValue; long consumed = Util.getMinimumSequence(gatingSequences, nextValue); long produced = nextValue; return getBufferSize() - (produced - consumed); }remainingCapacity方法就是环形队列的容量减去事件发布者与事件处理者的序列差。
@Override public void claim(long sequence){ this.nextValue = sequence; }claim方法是声明一个序列,在初始化的时候用。
@Override public void publish(long sequence){ cursor.set(sequence); waitStrategy.signalAllWhenBlocking(); } @Override public void publish(long lo, long hi){ publish(hi); }
发布一个序列,会先设置内部游标值,然后唤醒等待的事件处理者。
@Override public boolean isAvailable(long sequence){ return sequence <= cursor.get(); } @Override public long getHighestPublishedSequence(long lowerBound, long availableSequence){ return availableSequence; }
再看下MultiProducerSequencer,还是先看结构:
public final class MultiProducerSequencer extends AbstractSequencer{ private static final Unsafe UNSAFE = Util.getUnsafe(); private static final long BASE = UNSAFE.arrayBaseOffset(int[].class); private static final long SCALE = UNSAFE.arrayIndexScale(int[].class); private final Sequence gatingSequenceCache = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); // availableBuffer是用来记录每一个ringbuffer槽的状态。 private final int[] availableBuffer; private final int indexMask; private final int indexShift; public MultiProducerSequencer(int bufferSize, final WaitStrategy waitStrategy){ super(bufferSize, waitStrategy); availableBuffer = new int[bufferSize]; indexMask = bufferSize - 1; indexShift = Util.log2(bufferSize); initialiseAvailableBuffer(); }
MultiProducerSequencer内部多了一个availableBuffer,是一个int型的数组,size大小和RingBuffer的Size一样大,用来追踪Ringbuffer每个槽的状态,构造MultiProducerSequencer的时候会进行初始化,availableBuffer数组中的每个元素会被初始化成-1。
@Override public boolean hasAvailableCapacity(final int requiredCapacity){ return hasAvailableCapacity(gatingSequences, requiredCapacity, cursor.get()); } private boolean hasAvailableCapacity(Sequence[] gatingSequences, final int requiredCapacity, long cursorValue){ long wrapPoint = (cursorValue + requiredCapacity) - bufferSize; long cachedGatingSequence = gatingSequenceCache.get(); if (wrapPoint > cachedGatingSequence || cachedGatingSequence > cursorValue){ long minSequence = Util.getMinimumSequence(gatingSequences, cursorValue); gatingSequenceCache.set(minSequence); if (wrapPoint > minSequence){ return false; } } return true; }逻辑和前面SingleProducerSequencer内部一样,区别是这里使用了cursor.get(),里面获取的是一个volatile的value值。
@Override public long next(int n){ if (n < 1){ throw new IllegalArgumentException("n must be > 0"); } long current; long next; do{ current = cursor.get(); next = current + n; long wrapPoint = next - bufferSize; long cachedGatingSequence = gatingSequenceCache.get(); if (wrapPoint > cachedGatingSequence || cachedGatingSequence > current){ long gatingSequence = Util.getMinimumSequence(gatingSequences, current); if (wrapPoint > gatingSequence){ LockSupport.parkNanos(1); // TODO, should we spin based on the wait strategy? continue; } gatingSequenceCache.set(gatingSequence); }else if (cursor.compareAndSet(current, next)){ break; } }while (true); return next; }
逻辑还是一样,区别是里面的增加当前序列值是原子操作。
@Override public void publish(final long sequence){ setAvailable(sequence); waitStrategy.signalAllWhenBlocking(); } private void setAvailable(final long sequence){ setAvailableBufferValue(calculateIndex(sequence), calculateAvailabilityFlag(sequence)); } private void setAvailableBufferValue(int index, int flag){ long bufferAddress = (index * SCALE) + BASE; UNSAFE.putOrderedInt(availableBuffer, bufferAddress, flag); } private int calculateAvailabilityFlag(final long sequence){ return (int) (sequence >>> indexShift); } private int calculateIndex(final long sequence){ return ((int) sequence) & indexMask; }方法中会将当前序列值的可用状态记录到availableBuffer里面,而记录的这个值其实就是sequence除以bufferSize,也就是当前sequence绕buffer的圈数。
@Override public boolean isAvailable(long sequence){ int index = calculateIndex(sequence); int flag = calculateAvailabilityFlag(sequence); long bufferAddress = (index * SCALE) + BASE; return UNSAFE.getIntVolatile(availableBuffer, bufferAddress) == flag; } @Override public long getHighestPublishedSequence(long lowerBound, long availableSequence){ for (long sequence = lowerBound; sequence <= availableSequence; sequence++){ if (!isAvailable(sequence)){ return sequence - 1; } } return availableSequence; }
isAvailable方法也好理解了,getHighestPublishedSequence方法基于isAvailable实现。
- 大概了解了Sequencer的功能和实现,接下来看一下序列相关的一些类:
public interface SequenceBarrier{ /** * 等待一个序列变为可用,然后消费这个序列。. * 这货明显是给事件处理者使用的。 */ long waitFor(long sequence) throws AlertException, InterruptedException, TimeoutException; /** * 获取当前可以读取的序列值。 */ long getCursor(); /** * 当前栅栏是否发过通知。 */ boolean isAlerted(); /** * 通知事件处理者状态变化,然后停留在这个状态上,直到状态被清除。 */ void alert(); /** * 清楚通知状态。 */ void clearAlert(); /** * 检测是否发生了通知,如果已经发生了抛出AlertException异常。 */ void checkAlert() throws AlertException; }来看一下SequenceBarrier的实现ProcessingSequenceBarrier:
final class ProcessingSequenceBarrier implements SequenceBarrier{ //等待策略。 private final WaitStrategy waitStrategy; //这个域可能指向一个序列组。 private final Sequence dependentSequence; private volatile boolean alerted = false; private final Sequence cursorSequence; private final Sequencer sequencer; public ProcessingSequenceBarrier(final Sequencer sequencer, final WaitStrategy waitStrategy, final Sequence cursorSequence, final Sequence[] dependentSequences){ this.sequencer = sequencer; this.waitStrategy = waitStrategy; this.cursorSequence = cursorSequence; if (0 == dependentSequences.length){ dependentSequence = cursorSequence; }else{ dependentSequence = new FixedSequenceGroup(dependentSequences); } } @Override public long waitFor(final long sequence) throws AlertException, InterruptedException, TimeoutException{ //先检测报警状态。 checkAlert(); //然后根据等待策略来等待可用的序列值。 long availableSequence = waitStrategy.waitFor(sequence, cursorSequence, dependentSequence, this); if (availableSequence < sequence){ return availableSequence; //如果可用的序列值小于给定的序列,那么直接返回。 } //否则,要返回能安全使用的最大的序列值。 return sequencer.getHighestPublishedSequence(sequence, availableSequence); } @Override public long getCursor(){ return dependentSequence.get(); } @Override public boolean isAlerted(){ return alerted; } @Override public void alert(){ alerted = true; //设置通知标记 waitStrategy.signalAllWhenBlocking();//如果有线程以阻塞的方式等待序列,将其唤醒。 } @Override public void clearAlert(){ alerted = false; } @Override public void checkAlert() throws AlertException{ if (alerted){ throw AlertException.INSTANCE; } } }
再看一个SequenceGroup类:
public final class SequenceGroup extends Sequence{ private static final AtomicReferenceFieldUpdater<SequenceGroup, Sequence[]> SEQUENCE_UPDATER = AtomicReferenceFieldUpdater.newUpdater(SequenceGroup.class, Sequence[].class, "sequences"); private volatile Sequence[] sequences = new Sequence[0]; public SequenceGroup(){ super(-1); } /** * 获取序列组中最小的序列值。 */ @Override public long get(){ return Util.getMinimumSequence(sequences); } /** * 将序列组中所有的序列设置为给定值。 */ @Override public void set(final long value){ final Sequence[] sequences = this.sequences; for (int i = 0, size = sequences.length; i < size; i++){ sequences[i].set(value); } } /** * 添加一个序列到序列组,这个方法只能在初始化的时候调用。 * 运行时添加的话,使用addWhileRunning(Cursored, Sequence) */ public void add(final Sequence sequence){ Sequence[] oldSequences; Sequence[] newSequences; do{ oldSequences = sequences; final int oldSize = oldSequences.length; newSequences = new Sequence[oldSize + 1]; System.arraycopy(oldSequences, 0, newSequences, 0, oldSize); newSequences[oldSize] = sequence; } while (!SEQUENCE_UPDATER.compareAndSet(this, oldSequences, newSequences)); } /** * 将序列组中出现的第一个给定的序列移除。 */ public boolean remove(final Sequence sequence){ return SequenceGroups.removeSequence(this, SEQUENCE_UPDATER, sequence); } /** * 获取序列组的大小。 */ public int size(){ return sequences.length; } /** * 在线程已经开始往Disruptor上发布事件后,添加一个序列到序列组。 * 调用这个方法后,会将新添加的序列的值设置为游标的值。 */ public void addWhileRunning(Cursored cursored, Sequence sequence){ SequenceGroups.addSequences(this, SEQUENCE_UPDATER, cursored, sequence); } }
还有一个SequenceGroups类,是针对SequenceGroup的帮助类,里面提供了addSequences和removeSequence方法,都是原子操作。
- 最后总结:
上面看了这么多序列的相关类,其实只需要记住三点:
1.真正的序列是Sequence。
2.事件发布者通过Sequencer的大部分功能来使用序列。
3.事件处理者通过SequenceBarrier来使用序列。
相关推荐
不错的框架,可以好好研究研究,速度下载,速度下载速度下载速度下载
disruptor-3.2.0.jar包下载disruptor-3.2.0.jar包下载disruptor-3.2.0.jar包下载
disruptor-3.4.2.jar 工具jar包 及 disruptor-3.4.2-sources.jar, Disruptor它是一个开源的并发框架,并获得2011 Duke’s 程序框架创新奖,能够在无锁的情况下实现网络的Queue并发操作,是 log4j2 引用的 jar 包
disruptor-3.4.4.jar 官方github下载 亲测可用,大家赶紧下载吧 后续再补充其他常用jar(但不好下载的)
赠送jar包:disruptor-3.3.0.jar; 赠送原API文档:disruptor-3.3.0-javadoc.jar; 赠送源代码:disruptor-3.3.0-sources.jar; 赠送Maven依赖信息文件:disruptor-3.3.0.pom; 包含翻译后的API文档:disruptor-...
赠送jar包:disruptor-3.3.0.jar; 赠送原API文档:disruptor-3.3.0-javadoc.jar; 赠送源代码:disruptor-3.3.0-sources.jar; 赠送Maven依赖信息文件:disruptor-3.3.0.pom; 包含翻译后的API文档:disruptor-...
在"disruptor-3.2.1源码带jar包20140321"这个资源中,包含了Disruptor的源代码,这对于理解其内部机制和定制化开发非常有帮助。通过阅读源码,你可以更深入地了解如何利用Disruptor构建高效的并发系统。 此外,你还...
赠送jar包:disruptor-3.3.7.jar 赠送原API文档:disruptor-3.3.7-javadoc.jar 赠送源代码:disruptor-3.3.7-sources.jar 包含翻译后的API文档:disruptor-3.3.7-javadoc-API文档-中文(简体)-英语-对照版.zip ...
赠送jar包:disruptor-3.3.7.jar; 赠送原API文档:disruptor-3.3.7-javadoc.jar; 赠送源代码:disruptor-3.3.7-sources.jar; 赠送Maven依赖信息文件:disruptor-3.3.7.pom; 包含翻译后的API文档:disruptor-...
disruptor-3.4.2.jar
disruptor-3.3.11.jar 无锁并行框架 值得学习 jar包
Disruptor3.3.2是该框架的一个稳定版本,提供了更高的并发性能和更完善的特性。 在Java并发编程中,线程之间的数据共享通常会带来锁竞争,这会成为性能瓶颈。Disruptor通过消除锁和减少内存缓存失效,实现了线程间...
java运行依赖jar包
3. **API与源码解析** - `disruptor-3.0.1.jar`:这是Disruptor的运行时库,包含了框架的类和接口,供开发者在项目中引用。 - `disruptor-3.0.1-sources.jar`:提供源代码,帮助开发者理解内部实现,方便调试和...
《Disruptor-3.2.1与Play2Memcached:开源项目的魅力解析》 在IT行业中,开源项目一直是技术创新的重要推动力。这次我们要探讨的是两个极具影响力的开源项目——Disruptor-3.2.1和Play2Memcached。它们分别在并发...
disruptor-3.3.11-sources.jar jar包源码,值得学习,源码
(1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以...
disruptor-unity3d, Unity3d Disruptor的基本实现 disruptor-unity3dUnity3d的基本自包含。自包含实现。 仅支持单个生产者/单个用户。 仅在x86平台上测试。 Mono中的Bug 在Unity前可以在iOS和安卓上工作。用法将 ...
《Spring Boot Starter Disruptor深度解析》 在现代软件开发中,高性能和低延迟往往是系统设计的关键要素。Spring Boot作为Java领域最受欢迎的微服务框架,提供了丰富的启动器(starters)来简化开发工作。"spring-...