共计 7183 个字符,预计需要花费 18 分钟才能阅读完成。
欢送拜访我的 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
正文完