美文网首页
RingBuffer 缓存如何写?

RingBuffer 缓存如何写?

作者: Double_winter | 来源:发表于2019-06-16 11:29 被阅读0次

    需求: 我们经常使用缓存, ringBuffer 缓存 便是常见的一种数据结构。 支持高并发。 那问题来了,我们如何写个不错的ringBuffer 出来呢?

    写ringbuffer 之前需要理解下下面几个点:

    前提: 1.  原子变量如何保证线程安全? 比如 AtomicLong,  AtomicInteger,其原子性操作,such as:  updateAndGet(LongUnaryOperator updateFunction), 虽然updateFunction 是一个表达式,但是整个操作都是原子性质的。 具体如何保证,我也没详细研究。

             2.  尽量使用位移来操作, 加快速度, 比如 &, >>, << 等

              2. 伪共享,为什么会用到这个呢?  其实它跟CPU 缓存有关系,叫做 缓存行(cache line)。一般是64字节,需要8Long 整形的数据来填充。

    缓存行 状态图 如下: 

    好了,那么我们看看 ringBuffer 是什么样子的:

    tail 指针:

    表示Producer生产的最大序号(此序号从0开始,持续递增)。Tail不能超过Cursor,即生产者不能覆盖未消费的slot。当Tail已赶上cursor.

    cursor指针:

    表示Consumer消费到的最小序号(序号序列与Producer序列相同)。Cursor不能超过Tail,即不能消费未生产的slot。当Cursor已赶上tail。

    那我们怎么实现这样的数据结构呢?

    我们发现会有两个不同的线程(至少), 一个是不断的put 缓存内容,一个是不断take 缓存内容。 如果我们缓存的内容是一个Long 整型的数。

    其实这个ringBuffer它就是个数组,由于数组元素在内存中是连续分配的,可以利用CPU cache来提升性能, 但是就会带来上面所说的伪共享的问题。解决的方式就是cache line 补齐的方式。

    第一:

    定义二个long数组: slot[],存数据,flag[] 数组

    定义两个指针:     tail 指针, cursor 指针, 都是原子long整型的。初始化都是-1L

    put 操作:使用synchronized

    1. 获取 tail 指针现在的value

    2. 计算下一个 tail 执政的value 是多少

    3. 将slots[] 赋上缓存的值,flags 做个 “can take flag”

    4. tail 指针 加上一

    第二:

    take 缓存数据的 操作:

    1. 获取cursor 指针现在的value

    2. 和tail 只能value 比较,如果等于 tail 的value,表示 cursor 指针追赶到tail 指针,此时ringbuffer 是empty的。

    3. 计算下一个slot[] 的index 是多少,获取值就可以了。

    请记住 tail , cursor是long 原子整型, 他们是无限增加的,但是 

    第三:

    cache line 补齐的方式:

    ,我们知道一条缓存行有 64 字节,而 Java 程序的对象头固定占 8 字节(32位系统)或 12 字节( 64 位系统默认开启压缩, 不开压缩为 16 字节),所以我们只需要填 6 个无用的长整型补上6*8=48字节,让不同的 VolatileLong 对象处于不同的缓存行,就避免了伪共享( 64 位系统超过缓存行的 64 字节也无所谓,只要保证不同线程不操作同一缓存行就可以)。

    可以继承 AtomicLong, 子类里面定义6个Long 整型的,就可以避免伪共享的问题。

    RingBuffer 确实不好写,我觉得,而且里面的操作都是以位移为多数,不好弄。但是搞好,速度会很快的。自己水平不行,记录ringbuffer 大概操作过程,以后用的着。

    相关文章

      网友评论

          本文标题:RingBuffer 缓存如何写?

          本文链接:https://www.haomeiwen.com/subject/yspwfctx.html