背景

在多线程环境下为了保证线程安全,往往需要加锁,例如读写锁可以保证读写互斥,读读不互斥。有没有一种数据结构能够实现无锁的线程安全呢?答案就是使用RingBuffer循环队列。在Disruptor项目中就运用到了RingBuffer。

RingBuffer基本原理

在RingBuffer中设置了两个指针,head和tail。head指向下一次读的位置,tail指向的是下一次写的位置。RingBuffer可用一个数组进行存储,数组内元素的内存地址是连续的,这是对CPU缓存友好的----也就是说,在硬件级别,数组中的元素是会被预加载的,因此在RingBuffer中,CPU无需时不时去主内存加载数组中的下一个元素。通过对head和tail指针的移动,可以实现数据在数组中的环形存取。当head==tail时,说明buffer为空,当head==(tail+1)%bufferSize则说明buffer满了。

在进行读操作的时候,我们只修改head的值,而在写操作的时候我们只修改tail的值。在写操作时,我们在写入内容到buffer之后才修改tail的值,而在进行读操作的时候,我们会读取tail的值并将其赋值给copyTail。赋值操作是原子操作。所以在读到copyTail之后,从head到copyTail之间一定是有数据可以读的,不会出现数据没有写入就进行读操作的情况。同样的,读操作完成之后,才会修改head的数值;而在写操作之前会读取head的值来判断是否有空间可以用来写数据。所以,这时候tail到head-1之间一定是有空间可以写数据的,而不会出现一个位置的数据还没有读出就被写操作覆盖的情况。这样就保证了RingBuffer的线程安全性。

import java.util.Arrays;public class RingBuffer<T> {private final static int DEFAULT_SIZE  = 1024;private Object[] buffer;private int head = 0;private int tail = 0;private int bufferSize;public RingBuffer(){this.bufferSize = DEFAULT_SIZE;this.buffer = new Object[bufferSize];}public RingBuffer(int initSize){this.bufferSize = initSize;this.buffer = new Object[bufferSize];}private Boolean empty() {return head == tail;}private Boolean full() {return (tail + 1) % bufferSize == head;}public void clear(){Arrays.fill(buffer,null);this.head = 0;this.tail = 0;}public Boolean put(String v) {if (full()) {return false;}buffer[tail] = v;tail = (tail + 1) % bufferSize;return true;}public Object get() {if (empty()) {return null;}Object result = buffer[head];head = (head + 1) % bufferSize;return result;}public Object[] getAll() {if (empty()) {return new Object[0];}int copyTail = tail;int cnt = head < copyTail ? copyTail - head : bufferSize - head + copyTail;Object[] result = new String[cnt];if (head < copyTail) {for (int i = head; i < copyTail; i++) {result[i - head] = buffer[i];}} else {for (int i = head; i < bufferSize; i++) {result[i - head] = buffer[i];}for (int i = 0; i < copyTail; i++) {result[bufferSize - head + i] = buffer[i];}}head = copyTail;return result;}}

为什么要有环形缓冲器

当有大量数据的时候,我们不能存储所有的数据,那么计算机处理数据的时候,只能先处理先来的,那么处理完后呢,就会把数据释放掉,再处理下一个。那么,已经处理的数据的内存就会被浪费掉。因为后来的数据只能往后排队,如果要将剩余的数据都往前移动一次,那么效率就会低下了,肯定不现实,所以,环形队列就出现了。

频繁的内存分配不但增加了系统开销,更使得内存碎片不断增多,非常不利于我们的服务器长期稳定运行。也许我们可以使用内存池,比如SGI STL中附带的小内存分配器。但是对于这种按照严格的先进先出顺序处理的,块大小并不算小的,而且块大小也并不统一的内存分配情况来说,更多使用的是一种叫做环形缓冲区的方案。

区分缓冲区是否为满的策略

1.总是保持一个存储单元为空
        缓冲区中总是有一个存储单元保持未使用状态。缓冲区最多存入(size - 1) 个数据。如果读写指针指向同一位置,则缓冲区为空。如果写指针位于读指针的相邻后一个位置,则缓冲区为满。这种策略的优点是简单、鲁棒;缺点是语义上实际可存数据量与缓冲区容量不一致,测试缓冲区是否满需要做取余数计算。

2.使用数据计数
        这种策略不使用显式的写指针,而是保持着缓冲区内存储的数据的计数。因此测试缓冲区是空是满非常简单;对性能影响可以忽略。缺点是读写操作都需要修改这个存储数据计数,对于多线程访问缓冲区需要并发控制。

3.镜像指示位
        缓冲区的长度如果是n,逻辑地址空间则为0至n-1;那么,规定n至2n-1为镜像逻辑地址空间。本策略规定读写指针的地址空间为0至2n-1,其中低半部分对应于常规的逻辑地址空间,高半部分对应于镜像逻辑地址空间。当指针值大于等于2n时,使其折返(wrapped)到ptr-2n。使用一位表示写指针或读指针是否进入了虚拟的镜像存储区:置位表示进入,不置位表示没进入还在基本存储区。
        在读写指针的值相同情况下,如果二者的指示位相同,说明缓冲区为空;如果二者的指示位不同,说明缓冲区为满。这种方法优点是测试缓冲区满/空很简单;不需要做取余数操作;读写线程可以分别设计专用算法策略,能实现精致的并发控制。 缺点是读写指针各需要额外的一位作为指示位。
        这种方法的逻辑是,如果写指针超出了读指针n位,在普通情况下,读写指针重合,但在这种情况下,写指针在镜像空间的读指针+n位,不和读指针重合

如果缓冲区长度是2的幂,则本方法可以省略镜像指示位。如果读写指针的值相等,则缓冲区为空;如果读写指针相差n,则缓冲区为满,这可以用条件表达式(写指针 == (读指针 异或 缓冲区长度))来判断。

无锁环形缓存器RingBuffer的原理相关推荐

  1. 无锁环形队列的几种高效实现

    1.环形队列是什么 队列是一种常用的数据结构,这种结构保证了数据是按照"先进先出"的原则进行操作的,即最先进去的元素也是最先出来的元素.环形队列是一种特殊的队列结构,保证了元素也是 ...

  2. 无锁环形缓冲区的详细解释

    由以下博客的分析可以知道,内核的kfifo使用了很多技巧以实现其高效性.比如,通过限定写入的数据不能溢出和内存屏障实现在单线程写单线程读的情况下不使用锁.因为锁是使用在共享资源可能存在冲突的情况下.还 ...

  3. dpdk的无锁环形队列Ring

    1. DPDK Ring简介 dpdk实现了一个无锁环形队列Ring,可用于在dpdk不同的应用程序之间进行通信. Ring支持的效果: 先进先出 最大大小是固定的,指针存储在表中 无锁实现 多消费者 ...

  4. (精简理解)DPDK的无锁环形队列Ring

    这里写目录标题 1. DPDK Ring简介 2. Ring实现 2.1 单一生产者入队 2.2 单一消费者出队 2.3 多个生产者入队 2.4 多个消费者出队 3 总结 3.1 入队列 3.2 出队 ...

  5. 徐无忌并发编程笔记:无锁机制CAS及其底层实现原理?

    徐无忌并发编程笔记:无锁机制CAS及其底层实现原理? 完成:第一遍 1.什么是CAS算法? compare and swap:比较与交换,是一种有名的无锁算法 CAS带来了一种无锁解决线程同步,冲突问 ...

  6. 理解 Memory barrier(内存屏障)无锁环形队列

    Memory barrier 简介 程序在运行时内存实际的访问顺序和程序代码编写的访问顺序不一定一致,这就是内存乱序访问.内存乱序访问行为出现的理由是为了提升程序运行时的性能.内存乱序访问主要发生在两 ...

  7. 深入理解高并发技术dpdk无锁队列

    前两周给大家直播分享,并发技术全景(从硬件,操作系统,虚拟机/标准库,编程语言等) 上半场(5个小时):并发/并行技术全景指南 下半场(5个小时):人生的下半场,你准备好了吗 最后我上周还布置了一个作 ...

  8. Java并发编程,无锁CAS与Unsafe类及其并发包Atomic

    为什么80%的码农都做不了架构师?>>>    我们曾经详谈过有锁并发的典型代表synchronized关键字,通过该关键字可以控制并发执行过程中有且只有一个线程可以访问共享资源,其 ...

  9. Java并发编程-无锁CAS与Unsafe类及其并发包Atomic

    [版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/72772470 出自[zejian ...

  10. ZMQ无锁队列的原理与实现

    ZMQ无锁队列的原理与实现 前言 1. 为什么需要⽆锁队列 2. 无锁队列的实现(参考zmq,只支持一写一读的场景) 2.1 无锁队列前言 2.2 原⼦操作函数介绍 2.3 yqueue_t的chun ...

最新文章

  1. 2019阿里巴巴面试题+答案
  2. c#课程设计---猜猜看游戏
  3. 在Java中使用分隔符(拆分的对立面)连接数组元素的快速简便方法
  4. 面试题:接口和抽象类的区别
  5. mysql5.7+主从不一致_Mysql5.7安装错误处理与主从同步及!
  6. Ucenter会员同步通讯登录原理(转)
  7. [转]c#调用API截图
  8. jmeter(一)安装教程
  9. 基于VHDL超声波测距实验
  10. Oracle入门(十三A)之Select
  11. 新容器!不再担心空指针, Java8 Optional类
  12. parallelstudio license 位置_卫生间这3个位置95%装修没用好
  13. 活动服务器优化 暂时关闭,服务器优化之关闭不必要的服务
  14. 电脑怎么设计java环境_java环境变量配置,详细教您win7怎么配置java环境变量。
  15. sql相同顺序法和一次封锁法_不到75行代码,导出最高法指导案例到excel(一)...
  16. matlab贝塞尔函数零点,第一类贝塞尔函数零点求解matlab程序
  17. C语言实现反汇编【微机原理】
  18. Java2实用教程(第五版)/第五章例题
  19. java6发布_Minecraft Java版 1.13-pre6 发布
  20. C#生成word压缩下载

热门文章

  1. 台式计算机显卡驱动位置,台式电脑独立显卡怎么安装驱动的
  2. 衣带渐宽终不悔,为“指针”消得人憔悴(四)
  3. 马斯克狠狠地给马云上了一课 | 对话全文实录
  4. 随机生成中文姓名(分性别)
  5. 金融总结八---多头/空头平仓/开仓
  6. MHDD 4.6 硬盘工具简介、下载、启动盘制作、扫描及修复硬盘坏道、坏道修复原理简介、常用命令等
  7. NoteExpress
  8. android studio 添加 ignored files 忽略文件
  9. 解密为何 Golang 能从众多语言中脱颖而出
  10. Shallow Size、Retained Size、Heap Size 和 Allocated