RingBuffer 缓存如何写?

需求: 我们经常使用缓存, 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 缓存如何写?)