欢送拜访我的GitHub
https://github.com/zq2599/blog_demos
内容:所有原创文章分类汇总及配套源码,波及Java、Docker、Kubernetes、DevOPS等;
《disruptor笔记》系列链接
- 疾速入门
- Disruptor类剖析
- 环形队列的根底操作(不必Disruptor类)
- 事件生产知识点小结
- 事件生产实战
- 常见场景
- 期待策略
- 知识点补充(终篇)
本篇概览
本文是《disruptor笔记》的第七篇,咱们一起浏览源码,学习一个重要的知识点:期待策略,因为Disruptor的源码短小精干、简略易懂,因而本篇是个轻松愉快的源码学习之旅;
提前小结
如果您工夫不富余,能够通过以下提前小结的内容,对期待策略有个大体的意识:
- BlockingWaitStrategy:用了ReentrantLock的期待&&唤醒机制实现期待逻辑,是默认策略,比拟节俭CPU
- BusySpinWaitStrategy:继续自旋,JDK9之下慎用(最好别用)
- DummyWaitStrategy:返回的Sequence值为0,失常环境是用不上的
- LiteBlockingWaitStrategy:基于BlockingWaitStrategy,在没有锁竞争的时候会省去唤醒操作,然而作者说测试不充沛,不倡议应用
- TimeoutBlockingWaitStrategy:带超时的期待,超时后会执行业务指定的解决逻辑
- LiteTimeoutBlockingWaitStrategy:基于TimeoutBlockingWaitStrategy,在没有锁竞争的时候会省去唤醒操作
- SleepingWaitStrategy:三段式,第一阶段自旋,第二阶段执行Thread.yield交出CPU,第三阶段睡眠执行工夫,重复的的睡眠
- YieldingWaitStrategy:二段式,第一阶段自旋,第二阶段执行Thread.yield交出CPU
- PhasedBackoffWaitStrategy:四段式,第一阶段自旋指定次数,第二阶段自旋指定工夫,第三阶段执行Thread.yield交出CPU,第四阶段调用成员变量的waitFor办法,这个成员变量能够被设置为BlockingWaitStrategy、LiteBlockingWaitStrategy、SleepingWaitStrategy这三个中的一个
对于期待策略
- 回顾一下后面的文章中实例化Disruptor的代码:
disruptor = new Disruptor<>(new OrderEventFactory(), BUFFER_SIZE, new CustomizableThreadFactory("event-handler-"));
- 开展上述构造方法,会见到创立RingBuffer的代码,默认应用了BlockingWaitStrategy作为期待策略:
public static <E> RingBuffer<E> createMultiProducer(EventFactory<E> factory, int bufferSize) { return createMultiProducer(factory, bufferSize, new BlockingWaitStrategy()); }
- 持续开展下面的<font color="blue">createMultiProducer</font>办法,可见每个Sequencer(留神不是Sequence)都有本人的watStrategy成员变量:
- 这个waitStrategy的最终用处是创立SequenceBarrier的时候,传给SequenceBarrier做成员变量:
- 在看看SequenceBarrier是如何应用waitStrategy的,一共两处用到,第一处如下图红框,原来是<font color="blue">waitFor</font>办法外部会用到,这个<font color="blue">waitFor</font>咱们后面曾经理解过,对消费者来说,期待环形队列的指定地位有可用数据时,就是调用SequenceBarrier的waitFor实现的:
- SequenceBarrier第二处用到waitStrategy是唤醒的时候:
@Override public void alert() { alerted = true; waitStrategy.signalAllWhenBlocking(); }
- 当初咱们晓得了WaitStrategy的应用场景,接下来看看这个接口有哪些具体实现吧,这样咱们在编程中就晓得如何抉择才最适宜本人
BlockingWaitStrategy
- 作为默认的期待策略,BlockingWaitStrategy还有个特点就是代码量小(不到百行),很容易了解,其实就是用ReentrantLock+Condition来实现期待和唤醒操作的,如下图红框:
- 如果您更偏向于节俭CPU资源,对高吞吐量和低延时的要求绝对低一些,那么BlockingWaitStrategy就适宜您了;
BusySpinWaitStrategy(慎用)
- 后面的BlockingWaitStrategy有个特点,就是一旦环形队列指定地位来了数据,因为线程是期待状态(底层调用了native的UNSAFE.park办法),因而还要唤醒后能力执行业务逻辑,在一些场景中心愿数据一到就尽快生产,此时BusySpinWaitStrategy就很适合了,代码太简略,全副贴出:
public final class BusySpinWaitStrategy implements WaitStrategy{ @Override public long waitFor( final long sequence, Sequence cursor, final Sequence dependentSequence, final SequenceBarrier barrier) throws AlertException, InterruptedException { long availableSequence; while ((availableSequence = dependentSequence.get()) < sequence) { barrier.checkAlert(); ThreadHints.onSpinWait(); } return availableSequence; } @Override public void signalAllWhenBlocking() { }}
- 上述代码显示,整个while循环的要害就是<font color="blue">ThreadHints.onSpinWait</font>做了什么,源码如下,这里要分外留神,如果<font color="blue">ON_SPIN_WAIT_METHOD_HANDLE</font>为空,意味着里面的while循环是个<font color="red">十分耗费CPU的自旋</font>:
public static void onSpinWait() { if (null != ON_SPIN_WAIT_METHOD_HANDLE) { try { ON_SPIN_WAIT_METHOD_HANDLE.invokeExact(); } catch (final Throwable ignore) { } } }
- ON_SPIN_WAIT_METHOD_HANDLE为空是很可怕的事件,咱们来看看它是何方神圣?代码还是在ThreadHints.java中,如下所示,假相高深莫测,<font color="blue">它就是Thread类的onSpinWait办法</font>,如果Thread类没有onSpinWait办法,那么应用BusySpinWaitStrategy作为期待策略就有很高的代价了,环形队列里没有数据时生产线程会执行自旋,很消耗CPU:
static { final MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle methodHandle = null; try { methodHandle = lookup.findStatic(Thread.class, "onSpinWait", methodType(void.class)); } catch (final Exception ignore) { } ON_SPIN_WAIT_METHOD_HANDLE = methodHandle; }
- 好吧,还剩两个问题:Thread类有没有onSpinWait办法还不能确定吗?这个onSpinWait办法是何方神圣?
- 去看JDK官网文档,如下图,原来这办法是从JDK9才有的,所以对于JDK8使用者来说来说,选用BusySpinWaitStrategy就意味着要面对没做啥事儿的while循环了:
- 第二个问题,onSpinWait办法干了些啥?后面的官网文档,以欣宸的英语水平显然是无奈了解的,去看stackoverflow吧,如下图,简略的说,就是通知CPU以后线程处于循环查问的状态,CPU得悉后就会调度更多CPU资源给其余线程:
- 至此真像大白:环形队列的条件就绪后,BusySpinWaitStrategy策略是通过whlie死循环来做到疾速响应的,如果JDK是9或者更高版本,这个死循环带来的CPU损耗由Thread.onSpinWait帮忙缓解,如果JDK版本低于9,这里就是个简略的while死循环,至于这种死循环有多耗费CPU,您能够写段简略代码感受一下...
- 难怪Disruptor源码中会揭示<font color="blue">最好是将应用此实例的线程绑定到指定CPU核</font>:
DummyWaitStrategy
固定返回0,集体感觉这个策略在失常开发中用不上,因为环形队列可用地位始终是0的话,不论是生产还是生产都难以实现:
LiteBlockingWaitStrategy
- 看名字,LiteBlockingWaitStrategy是BlockingWaitStrategy策略的轻量级实现,在锁没有竞争的时候(例如独立生产的场景),会省略掉唤醒操作,不过如下图红框所示,作者说他没有充沛验证过正确性,因而倡议只用于体验,太好了,这个策略<font color="red">我不学了!!!</font>
TimeoutBlockingWaitStrategy
- 顾名思义,TimeoutBlockingWaitStrategy示意只期待某段时长,超过了就算超时,其代码和BlockingWaitStrategy相似,只是期待的时候有个时长限度,如下图,高深莫测:
- 其实我对抛出异样后的解决很感兴趣,去看看吧,里面是相熟的BatchEventProcessor类,相熟的processEvents办法,如下图,每次超时异样都交给<font color="blue">notifyTimeout</font>解决,而内部的主流程不受影响,仍旧一直的从环形队列中期待和获取数据:
- 进入notifyTImeout办法,可见实际上是交给成员变量<font color="blue">timeoutHandler</font>去解决的,而且处理过程中产生的任何异样都会被捕捉,不会抛出去影响内部调用:
- 再来看看成员变量是哪来的,如下图,水落石出,咱们开发的EventHandler实现类,如果也实现了Timeouthandler,就被当做成员变量timeoutHandler了:
- 至此TimeoutBlockingWaitStrategy也搞清楚了:用于有工夫限度的场景,每次期待超时后都会调用业务定制的超时解决逻辑,这个逻辑写到EventHandler实现类中,这个实现类要实现Timeouthandler接口
LiteTimeoutBlockingWaitStrategy
- LiteTimeoutBlockingWaitStrategy与TimeoutBlockingWaitStrategy的关系,就像BlockingWaitStrategy与LiteBlockingWaitStrategy的关系:作为TimeoutBlockingWaitStrategy的变体,有TimeoutBlockingWaitStrategy的超时解决个性,而且没有锁竞争的时候,省略掉唤醒操作;
- 作者说LiteBlockingWaitStrategy可用于体验,但正确性并未通过充沛验证,然而在LiteTimeoutBlockingWaitStrategy的正文中<font color="red">没有看到这种说法</font>,看样子这是个靠谱的期待策略,能够用,用在有超时解决的需要,而且没有锁竞争的场景(例如独立生产)
SleepingWaitStrategy
- 和后面几个不同的是,SleepingWaitStrategy没有用到锁,这象征这无需调用signalAllWhenBlocking办法做唤醒解决,相当于省去了生产线程的告诉操作,官网源码正文有这么句话引起了我的趣味,如下图红框,粗心是该策略在性能和CPU资源耗费之间获得了均衡,接下来去看看要害代码,来理解这个个性:
- 如下图,等到可用数据的过程是个死循环:
- 接下来是要害代码了,如下图,可见整个期待过程分为三段:计数器高于100时就只有一个减一的操作(最快响应),计数器在100到0之间时每次都交出CPU执行工夫(最省资源),其余时候就睡眠固定工夫:
YieldingWaitStrategy
- 看过SleepingWaitStrategy之后,再看YieldingWaitStrategy就很容易了解了,和SleepingWaitStrategy相比,YieldingWaitStrategy先做指定次数的自旋,而后一直的交出CPU工夫:
- 因为在一直的执行<font color="blue">Thread.yield()</font>办法,因而该策略尽管很耗费CPU,不过一旦其余线程有CPU需要,很容易从这个线程失去;
PhasedBackoffWaitStrategy
- 最初是PhasedBackoffWaitStrategy,该策略的特点是将整个期待过程分成下图的四段,四个方块代表一个工夫线上的四个阶段:
- 这里阐明一下上图的四个阶段:
- 首先是自旋指定的次数,默认10000次;
- 自旋过后,开始带计时的自旋,执行的时长是spinTimeoutNanos的值;
- 执行时长达到spinTimeoutNanos的值后,开始执行<font color="blue">Thread.yield()</font>交出CPU资源,这个逻辑的执行时长是<font color="red">yieldTimeoutNanos-spinTimeoutNanos</font>;
- 执行时长达到<font color="red">yieldTimeoutNanos-spinTimeoutNanos</font>的值后,开始调用<font color="blue">fallbackStrategy.waitFor</font>,这个调用没有工夫或者次数限度;
- 当初问题来了<font color="blue">fallbackStrategy</font>是何方神圣?PhasedBackoffWaitStrategy类筹备了三个静态方法,咱们能够按需选用,让fallbackStrategy是BlockingWaitStrategy、LiteBlockingWaitStrategy、SleepingWaitStrategy这三个中的一个:
public static PhasedBackoffWaitStrategy withLock( long spinTimeout, long yieldTimeout, TimeUnit units) { return new PhasedBackoffWaitStrategy( spinTimeout, yieldTimeout, units, new BlockingWaitStrategy()); }public static PhasedBackoffWaitStrategy withLiteLock( long spinTimeout, long yieldTimeout, TimeUnit units) { return new PhasedBackoffWaitStrategy( spinTimeout, yieldTimeout, units, new LiteBlockingWaitStrategy()); } public static PhasedBackoffWaitStrategy withSleep( long spinTimeout, long yieldTimeout, TimeUnit units) { return new PhasedBackoffWaitStrategy( spinTimeout, yieldTimeout, units, new SleepingWaitStrategy(0)); }
- 至此,Disruptor的九种期待策略就全副剖析结束了,除了选用期待策略的时候更加得心应手,还有个播种就是积攒了浏览优良源码的教训,在读源码的路上更加有信念了;
你不孤独,欣宸原创一路相伴
- Java系列
- Spring系列
- Docker系列
- kubernetes系列
- 数据库+中间件系列
- DevOps系列
欢送关注公众号:程序员欣宸
微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游Java世界...
https://github.com/zq2599/blog_demos