Disruptor 之续写

本文详细介绍了Disruptor框架中生产者如何无锁、安全地将事件写入RingBuffer的过程。首先通过Sequencer获取下一个可写入事件的位置,并确保不会发生数据覆盖。其次,利用CAS操作保证多线程环境下写入RingBuffer的安全性。

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

通过查看有关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 无锁、无竞争、非常的快。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值