首页 > 编程知识 正文

javabytebuffer使用,java悲观锁的实现方式

时间:2023-05-04 15:00:07 阅读:34457 作者:4918

在多线程环境中,为了确保线程的安全,需要进行锁定。 例如,读写锁定保证读写排他,而读写不是排他。 有实现无锁定线程安全的数据结构吗? 答案是使用RingBuffer循环队列。 在Disruptor项目中利用了RingBuffer。

RingBuffer的基本原理如下。

RingBuffer提供了两个指针:头部和尾部。 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; publicclassringbuffert { privatefinalstaticintdefault _ size=1024; 私有对象[ ] buffer; 隐私保护头=0; 隐私等待时间=0; 私密int buffer size; publicringbuffer ((this.buffer size=default _ size; this.buffer=new object [ buffer size ]; }publicringbuffer(intinitsize ) { this.bufferSize=initSize; this.buffer=new object [ buffer size ]; }私密布尔empty () { return head==tail; }专用布尔完整() return ) tail1) % bufferSize==head; }公共void clear () Arrays.fill ) ) buffer,null ); this.head=0; this.tail=0; }publicbooleanput(stringv ) if (full ) ) { return false; } buffer[tail]=v; tail=(tail1) % bufferSize; 返回真; 公共对象get () if (empty ) ) ) {返回空值; } Object result=buffer[head]; head=(head1) % bufferSize; 返回结果; }公共对象[ ] get all () if (empty ) ) )返回新对象(0); } int copyTail=tail; int cnt=head copyTail? 复制tail-head :缓冲器大小- headcopytail; 对象[ ] result=new string [ CNT ]; if(headcopytail ) for ) intI=head; I复印泰il; 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; }}

RingBuffer解决粘包问题:

TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。粘包可能由发送方造成,也可能由接收方造成。TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据,造成多个数据包的粘连。如果接收进程不及时接收数据,已收到的数据就放在系统接收缓冲区,用户进程读取数据时就可能同时读到多个数据包。因为系统传输的数据是带结构的数据,需要做分包处理。

 

为了适应高速复杂网络条件,我们设计实现了粘包处理模块,由接收方通过预处理过程,对接收到的数据包进行预处理,将粘连的包分开。为了方便粘包处理,提高处理效率,在接收环节使用了环形缓冲区来存储接收到的数据。其结构如表1所示。

                                                            表1 环形缓冲结构

字段名

类型

含义

CS

CRITICAL_SECTION

保护环形缓冲的临界区

pRingBuf

UINT8*

缓冲区起始位置

pRead

UINT8*

当前未处理数据的起始位置

pWrite

UINT8*

当前未处理数据的结束位置

pLastWrite

UINT8*

当前缓冲区的结束位置

环形缓冲跟每个TCP套接字绑定。在每个TCP的SOCKET_OBJ创建时,同时创建一个PRINGBUFFER结构并初始化。这时候,pRingBuf指向环形缓冲区的内存首地址,pRead、pWrite指针也指向它。pLastWrite指针在这时候没有实际意义。初始化之后的结构如图1所示。

 

                                  图1 初始化后的环形缓冲区

在每次投递一个TCP的接收操作时,从RINGBUFFER获取内存作接收缓冲区,一般规定一个最大值L1作为可以写入的最大数据量。这时把pWrite的值赋给BUFFER_OBJ的buf字段,把L1赋给bufLen字段。这样每次接收到的数据就从pWrite开始写入缓冲区,最多写入L1字节,如图 2。

                                   图2 分配缓冲后的环形缓冲

如果某次分配过程中,pWrite到缓冲区结束的位置pEnd长度不够最小分配长度L1,为了提高接收效率,直接废弃最后一段内存,标记pLastWrite为pWrite。然后从pRingBuf开始分配内存,如图 3。

                                   图 3 使用到结尾的环形缓冲

特殊情况下,如果处理包速度太慢,或者接收太快,可能导致未处理包占用大部分缓冲区,没有足够的缓冲区分配给新的接收操作,如图4。这时候直接报告错误即可。

                                  图 4 没有足够接收缓冲的环形缓冲

当收到一个长度为L数据包时,需要修改缓冲区的指针。这时候已经写入数据的位置变为(pWrite+L),如图 5。

                                  图 5 收到长度为L的数据的环形缓冲

分析上述环形缓冲的使用过程,收到数据后的情况可以简单归纳为两种:pWrite>pRead,接收但未处理的数据位于pRead到pWrite之间的缓冲区;pWrite<pRead,这时候,数据位于pRead到pLastWrite和pRingbuf到pWrite之间。这两种情况分别对应图6、图 7。

首先分析图6。此时,pRead是一个包的起始位置,如果L1足够一个包头长度,就获取该包的长度信息,记为L。假如L1>L,就说明一个数据包接收完成,根据包类型处理包,然后修改pRead指针,指向下一个包的起始位置(pRead+L)。这时候仍然类似于之前的状态,于是解包继续,直到L1不足一个包的长度,或者不足包头长度。这时退出解包过程,等待后续的数据到来。

                                图 6 有未处理数据的环形缓冲(1)

                              图 7 有未处理数据的环形缓冲(2)

图 8稍微复杂。首先按照上述过程处理L1部分。存在一种情况,经过若干个包处理之后,L1不足一个包,或者不足一个包头。如果这时(L1+L2)足够一个包的长度,就需要继续处理。另外申请一个最大包长度的内存区pTemp,把L1部分和L2的一部分复制到pTemp,然后执行解包过程。

经过上述解包之后,pRead就转向pRingBuf到pWrite之间的某个位置,从而回归情况图 6,继续按照图 6部分执行解包。

 

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。