欢送拜访我的GitHub

https://github.com/zq2599/blog_demos

内容:所有原创文章分类汇总及配套源码,波及Java、Docker、Kubernetes、DevOPS等;

《disruptor笔记》系列链接

  1. 疾速入门
  2. Disruptor类剖析
  3. 环形队列的根底操作(不必Disruptor类)
  4. 事件生产知识点小结
  5. 事件生产实战
  6. 常见场景
  7. 期待策略
  8. 知识点补充(终篇)

本篇概览

本文是《disruptor笔记》的第七篇,咱们一起浏览源码,学习一个重要的知识点:期待策略,因为Disruptor的源码短小精干、简略易懂,因而本篇是个轻松愉快的源码学习之旅;

提前小结

如果您工夫不富余,能够通过以下提前小结的内容,对期待策略有个大体的意识:

  1. BlockingWaitStrategy:用了ReentrantLock的期待&&唤醒机制实现期待逻辑,是默认策略,比拟节俭CPU
  2. BusySpinWaitStrategy:继续自旋,JDK9之下慎用(最好别用)
  3. DummyWaitStrategy:返回的Sequence值为0,失常环境是用不上的
  4. LiteBlockingWaitStrategy:基于BlockingWaitStrategy,在没有锁竞争的时候会省去唤醒操作,然而作者说测试不充沛,不倡议应用
  5. TimeoutBlockingWaitStrategy:带超时的期待,超时后会执行业务指定的解决逻辑
  6. LiteTimeoutBlockingWaitStrategy:基于TimeoutBlockingWaitStrategy,在没有锁竞争的时候会省去唤醒操作
  7. SleepingWaitStrategy:三段式,第一阶段自旋,第二阶段执行Thread.yield交出CPU,第三阶段睡眠执行工夫,重复的的睡眠
  8. YieldingWaitStrategy:二段式,第一阶段自旋,第二阶段执行Thread.yield交出CPU
  9. 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,该策略的特点是将整个期待过程分成下图的四段,四个方块代表一个工夫线上的四个阶段:

  • 这里阐明一下上图的四个阶段:
  1. 首先是自旋指定的次数,默认10000次;
  2. 自旋过后,开始带计时的自旋,执行的时长是spinTimeoutNanos的值;
  3. 执行时长达到spinTimeoutNanos的值后,开始执行<font color="blue">Thread.yield()</font>交出CPU资源,这个逻辑的执行时长是<font color="red">yieldTimeoutNanos-spinTimeoutNanos</font>;
  4. 执行时长达到<font color="red">yieldTimeoutNanos-spinTimeoutNanos</font>的值后,开始调用<font color="blue">fallbackStrategy.waitFor</font>,这个调用没有工夫或者次数限度;
  5. 当初问题来了<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的九种期待策略就全副剖析结束了,除了选用期待策略的时候更加得心应手,还有个播种就是积攒了浏览优良源码的教训,在读源码的路上更加有信念了;

你不孤独,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

欢送关注公众号:程序员欣宸

微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游Java世界...
https://github.com/zq2599/blog_demos