disruptor RingBuffer初始化与生产者事件产生

在Disruptor中,为了防止伪共享导致的性能降低,所有元素都会在前后尽量填充64个字节以保证在cpu以64字节缓存数据的时候,在缓存行中,都只会有自己所需要的数据,不会导致缓冲行的更新影响到别的cpu,以空间换时间,保证性能的提升。

在RingBuffer的初始化中,在其父类RingBufferFields的静态代码块及构造方法中,也可以看到为了消除伪共享而进行的内存填充。

static
{
    final int scale = UNSAFE.arrayIndexScale(Object[].class);
    if (4 == scale)
    {
        REF_ELEMENT_SHIFT = 2;
    }
    else if (8 == scale)
    {
        REF_ELEMENT_SHIFT = 3;
    }
    else
    {
        throw new IllegalStateException("Unknown pointer size");
    }
    BUFFER_PAD = 128 / scale;
    // Including the buffer pad in the array base offset
    REF_ARRAY_BASE = UNSAFE.arrayBaseOffset(Object[].class) + 128;
}

在RingBuffer存放具体的事件数组entries中,会在数组前后分别填充128个字节(尤其是首尾的元素),同时根据该jvm中数组单个元素的字节大小来确定填充的元素个数,在具体的申请数组空间的时候会用到。

同时,当通过Unsafe来通过偏移量来获取具体数组元素的时候,在起点需要偏移量需要先前进128字节,才是真正的第一个事件的具体位置。

 

再看RingBufferFields的构造方法。

RingBufferFields(
        EventFactory<E> eventFactory,
        Sequencer sequencer)
{
    this.sequencer = sequencer;
    this.bufferSize = sequencer.getBufferSize();

    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.indexMask = bufferSize - 1;
    this.entries = new Object[sequencer.getBufferSize() + 2 * BUFFER_PAD];
    fill(eventFactory);
}

    private void fill(EventFactory<E> eventFactory)
    {
        for (int i = 0; i < bufferSize; i++)
        {
            entries[BUFFER_PAD + i] = eventFactory.newInstance();
        }
    }

由于RingBuffer是一个环形队列,其具体的定位实则是通过序列号与总长度取模,由此,当长度为2的幂次方后,只需要将序列号与总长度减一进行相与,即可快速得到其应该在这个数组中的位置。在根据队列长度和填充元素的个数申请完数组之后,将会通过fill()方法一次性将数组全部填充,后续事件的增加修改只会直接修改数组中事件的字段,不会再涉及内存的申请与回收。

protected final E elementAt(long sequence)
{
    return (E) UNSAFE.getObject(entries, REF_ARRAY_BASE + ((sequence & indexMask) << REF_ELEMENT_SHIFT));
}

elementAt()方法实现了根据具体的序列号来获取环形队列的具体元素,根据之前移动了128字节作为起始位置,序列号与队列长度减一相与的结果为队列的具体位置下标,根据单个元素所占字节数,来在连续内存上获取具体的位置。

 

具体的序列号实现类,也是通过内存填充来实现消除伪共享的。

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;
}

实际序列号value将会前后分别填充7个long 类型分别是56字节,以保证前后加上value实例能够填满64字节,消除伪共享。

 

生产者是如何产生事件的?

当生产者准备通过publishEvnet()方法产生事件的时候,首先根据,会根据sequencer来通过next()方法来获取将事件所存放的下标。

单个消费者的时候next()方法由SingleProducerSequencer来实现。

public long next(int n)
{
    if (n < 1 || n > bufferSize)
    {
        throw new IllegalArgumentException("n must be > 0 and < bufferSize");
    }

    long nextValue = this.nextValue;

    long nextSequence = nextValue + n;
    long wrapPoint = nextSequence - bufferSize;
    long cachedGatingSequence = this.cachedValue;

    if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue)
    {
        cursor.setVolatile(nextValue);  // StoreLoad fence

        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;
}

其实现比较简单,直接将当前序列号加上就可以返回,需要注意的是,如果是其产生事件的并还未消费的数量已经超过了队列的长度将会进行自旋,直到有位置可以存放新的事件。

而多生产者的情况下,则是在其基础上增加了序列号的cas操作,确保能够获得其序列号不会和别的生产者冲突。

在获得了其序列号后,只需要根据上方的elementAt()方法获取数组上具体的内存空间,将新的事件覆盖在这个位置上即可。

在完成事件的写入,通过生产者的publish()通知消费者进行消费。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值