Disruptor核心类
阅读代码之前,建议大家先把Disruptor run起来,这样对于这个组件的一些核心概念就了解了,下面首先分析一下disruptor的核心类及其功能:
- Ring Buffer: disruptor的数据结构,一个环形队列,主要负责放入生产的消息,会在初始化的时候创建好所有event对象,以减少垃圾回收
- Sequence: 序列器,Sequence本身提供了类似AtomicLong的各个特性。生产者,消费者都是这个类的使用者,每个使用者都会维护一个Sequence来标识自己的读/写下标。,disruptor里面大部分的并发代码都是通过对Sequence的值同步修改实现的,而非锁,这是disruptor高性能的一个主要原因.
- Sequencer: disruptor里生产同步的实现者,Sequencer有单生产者,多生产者两种不同的模式,里面实现了各种同步的算法
- Sequence Barrier: Sequence Barrier是由Sequencer创建的,并被Processor持有,主要用来判断Processor是否可以拿到待处理的事件(生产是否到位,前置Processor是否消费完成,Processor是否被停止)
- Wait Strategy: disruptor提供了多个等待策略,比如阻塞等待唤醒的BlockingWaitStrategy,疯狂压榨cpu自旋追求响应时间的YieldingWaitStrategy(类似于synchronize的重量级锁,和轻量级锁的思路但是没有锁的升级逻辑)。
- Event: disruptor中传递的事件.
- EventProcessor/workProcessor: 处理Event的循环,在循环中获取Disruptor的事件,然后把事件分配给各个handler
- EventHandler: 负责业务逻辑的handler,由使用者自己实现
- Producer: 生成event事件的生产者
RingBuffer
首先我们先看这个java文件,这里面有个三个类:
- RingBufferPad
- RingBufferFields
- RingBuffer
使用了内存缓冲的优势提升性能
RingBufferPad从名字可以看出,padding英文为“垫料”的意思,所以这个类就是单纯的为了防止“伪共享”而进行内存填充的类。
关于“伪共享”这里引用一下美团技术博客的一个例子来证实下内存填充的高性能:
public class CacheLineEffect {
//考虑一般缓存行大小是64字节,一个 long 类型占8字节
static long[][] arr;
public static void main(String[] args) {
arr = new long[1024 * 1024][];
for (int i = 0; i < 1024 * 1024; i++) {
arr[i] = new long[8];
for (int j = 0; j < 8; j++) {
arr[i][j] = 0L;
}
}
long sum = 0L;
long marked = System.currentTimeMillis();
// 此处使用的内存空间为:(1024*1024)个大小为(8*8)字节的空间,每个大小为64字节的空间都是连续的。
for (int i = 0; i < 1024 * 1024; i+=1) {
for(int j =0; j< 8;j++){
sum = arr[i][j];
}
}
System.out.println("Loop times:" + (System.currentTimeMillis() - marked) + "ms");
marked = System.currentTimeMillis();
// 此处使用的内存空间为:(8)个大小为(1024*1024*8)字节的空间,每个大小为(1024*1024*8)字节的空间都是连续的。
for (int i = 0; i < 8; i+=1) {
for(int j =0; j< 1024 * 1024;j++){
sum = arr[j][i];
}
}
System.out.println("Loop times:" + (System.currentTimeMillis() - marked) + "ms");
}
}
上述例子中,第一次for循环利用了缓存行的优势,第二次没有利用缓存行的优势,执行结果如下所示:
Loop times:16ms
Loop times:90ms
关于伪共享的更多内容请大家自行了解。
再看下RingBuffer类的源代码
```java
abstract class RingBufferPad
{
protected long p1, p2, p3, p4, p5, p6, p7;
}
public final class RingBuffer<E> extends RingBufferFields<E> implements Cursored, EventSequencer<E>, EventSink<E>
{
public static final long INITIAL_CURSOR_VALUE = Sequence.INITIAL_VALUE;
protected long p1, p2, p3, p4, p5, p6, p7;
...省略n行
}
有人可能会困惑,为啥这里是7个long呢,CPU缓存行不是8个long吗?
这个地方我也不是很懂,以下是个人见解(还请高人评论区指点):这里是让RingBuffer类的变量在内存中前后各有7个long,这样存储使得RingBuffer类的变量如果缓存在CPU缓存行中,不会因为缓存行前后变量的是失效而被动失效,即重新刷新。
RingBuffer将下标操作抽象为Sequencer
阅读RingBuffer类可以发现,该类的核心属性只有一个entries,用来存储event。所有关于下标的操作全部放到了Sequencer中。这样有什么好处呢?
- RingBuffer和Sequencer采用组合的方式使用,降低了两者的耦合性,各自提高了内聚性;RingBuffer不用操心Sequencer内部是怎么做的,只要调用其接口就好。Sequencer内部可以写一千种实现,但是只要定义好的接口不变,就不会影响RingBuffer的使用。
- 将下标操作单独封装一个类,提升了扩展性。目前只有两种Sequencer:SingleProducerSequencer和MultiProducerSequencer,如果以后还有第三种Sequencer,这样设计就可以很好地扩展未来的业务变化。也符合开闭原则(面向接口关闭,面向扩展开放)。
Sequence
关于set和setVolatile方法
public void set(final long value)
{
UNSAFE.putOrderedLong(this, VALUE_OFFSET, value);
}
set方法使用unsafe的putOrderedLong方法,实现了类似AtomicLong.lazySet。
有一个问题,value的类型为volatile,为啥还要有putLongVolatile方法呢?
class Value extends LhsPadding
{
protected volatile long value;
}
...省略
public void setVolatile(final long value)
{
UNSAFE.putLongVolatile(this, VALUE_OFFSET, value);
}
value字段可以利用volatile内存屏障的特性来实现volatile写,这里为啥还要调用UNSAFE.putLongVolatile方法呢?这个问题百思不得其解。