通过查看有关Disruptor的资料以及前面的两篇博客, 个人觉得的还是写下自己对Disruptor的理解吧。
主要阐述下生产者将事件写入ringBuffer的过程吧,分为两个步骤:
1. 获取下一个可写入事件的sequence
2.sequence对应的区块/槽
3. 向槽中写入数据
4. 发布事件
public void setOnData(ByteBuffer bb){
// 1. 获取ringBuffer的下一个区块(槽/ 索引)
long sequence = ringBuffer.next();
try{
//2. 获取区块(槽/ 索引)所对应的的Entry
LongEvent event = ringBuffer.get(sequence);
//3.获取要通过事件传递的业务数据 设置值
event.setValue( Long.valueOf(new Random().nextInt(1000)));
event.setName("数据"+bb.getLong(0));
}finally{
//4.发布事件
//注意,最后的 ringBuffer.publish 方法必须包含在 finally 中以确保必须得到调用;
//如果某个请求的 sequence 未被提交,将会堵塞后续的发布操作或者其它的 producer。
ringBuffer.publish(sequence);
}
}
以上涉及两个问题:
1. 如何确保写入时不覆盖数据
2. 多线程环境下,如何保证线程安全写入
第一: 在生产者从ringBuffer获取下一个可用的 entry时,RingBuffer会将获取下一个可用的entry委托给Sequencer以通过调用next()来实现。
通过当前期望获取的sequence与gatingSequenceCache.get()获取的最小sequence做比较,以及 waitStrategy 策略 确保获取的sequence是最小且避免数据覆盖的可用序号。
long cachedGatingSequence = gatingSequenceCache.get();
就是缓存的消费者中最小序号值,他不是当前最新的‘消费者中最小序号值’,而是上次程序进入到下面的if判定代码段是,被赋值的当时的‘消费者中最小序号值’
/**
* @see Sequencer#next()
*/
@Override
public long next()
{
return next(1);
}
/**
* @see Sequencer#next(int)
*/
@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;
}
第二:Disruptor 唯一需要处理访问冲突的地方,是多个生产者写入 Ring Buffer 的场景。
Disruptor 不使用锁,因为锁竞争性能低。
多线程环境下,确保操作是线程安全的(特别是,在多生产者的环境下,更新下一个可用的序列号)地方,我们使用CAS(Compare And Swap/Set)操作。这是一个CPU级别的指令,它的工作方式有点像乐观锁——CPU去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。
生产者中的ClaimStrategy策略。可以看见两个策略,一个是SingleThreadedStrategy(单线程策略)另一个是MultiThreadedStrategy(多线程策略)。多线程的那个使用了AtomicLong(Java提供的CAS操作),而单线程的使用long,没有锁也没有CAS。这意味着单线程版本会非常快,因为它只有一个生产者,不会产生序号上的冲突。
多线程的那个使用了AtomicLong(Java提供的CAS操作),如果两个不同的并发变量位于同一个缓存行,则在并发情况下,会互相影响到彼此的缓存有效性,进而影响到性能,这叫着‘伪共享’。为了避开‘伪共享’,Disruptor3.0在Sequence.java中使用多个long变量填充,从而确保一个序号独占一个缓存行。
所以Disruptor 无锁、无竞争、非常的快。