本文主要研究下FluxInterval的机制

FluxInterval

reactor-core-3.1.3.RELEASE-sources.jar!/reactor/core/publisher/FluxInterval.java

/**
* Periodically emits an ever increasing long value either via a ScheduledExecutorService
* or a custom async callback function
* @see <a href="https://github.com/reactor/reactive-streams-commons">Reactive-Streams-Commons</a>
*/
final class FluxInterval extends Flux<Long> { final Scheduler timedScheduler; final long initialDelay; final long period; final TimeUnit unit; FluxInterval(
long initialDelay,
long period,
TimeUnit unit,
Scheduler timedScheduler) {
if (period < 0L) {
throw new IllegalArgumentException("period >= 0 required but it was " + period);
}
this.initialDelay = initialDelay;
this.period = period;
this.unit = Objects.requireNonNull(unit, "unit");
this.timedScheduler = Objects.requireNonNull(timedScheduler, "timedScheduler");
} @Override
public void subscribe(CoreSubscriber<? super Long> actual) {
Worker w = timedScheduler.createWorker(); IntervalRunnable r = new IntervalRunnable(actual, w); actual.onSubscribe(r); try {
w.schedulePeriodically(r, initialDelay, period, unit);
}
catch (RejectedExecutionException ree) {
if (!r.cancelled) {
actual.onError(Operators.onRejectedExecution(ree, r, null, null,
actual.currentContext()));
}
}
}
}

可以看到这里利用Scheduler来创建一个定时调度任务IntervalRunnable

IntervalRunnable

	static final class IntervalRunnable implements Runnable, Subscription,
InnerProducer<Long> {
final CoreSubscriber<? super Long> actual; final Worker worker; volatile long requested;
static final AtomicLongFieldUpdater<IntervalRunnable> REQUESTED =
AtomicLongFieldUpdater.newUpdater(IntervalRunnable.class, "requested"); long count; volatile boolean cancelled; IntervalRunnable(CoreSubscriber<? super Long> actual, Worker worker) {
this.actual = actual;
this.worker = worker;
} @Override
public CoreSubscriber<? super Long> actual() {
return actual;
} @Override
@Nullable
public Object scanUnsafe(Attr key) {
if (key == Attr.CANCELLED) return cancelled; return InnerProducer.super.scanUnsafe(key);
} @Override
public void run() {
if (!cancelled) {
if (requested != 0L) {
actual.onNext(count++);
if (requested != Long.MAX_VALUE) {
REQUESTED.decrementAndGet(this);
}
} else {
cancel(); actual.onError(Exceptions.failWithOverflow("Could not emit tick " + count + " due to lack of requests" +
" (interval doesn't support small downstream requests that replenish slower than the ticks)"));
}
}
} @Override
public void request(long n) {
if (Operators.validate(n)) {
Operators.addCap(REQUESTED, this, n);
}
} @Override
public void cancel() {
if (!cancelled) {
cancelled = true;
worker.dispose();
}
}
}

这里重点看requested变量,run方法每次判断requested,如果requested为0则销毁worker,否则则每次发射一个元素计数就减一
而subscriber如果有继续request的话,则会增加requested的值

实例1

    public static void main(String[] args) throws InterruptedException {
Flux<Long> flux = Flux.interval(Duration.ofMillis(1))
.doOnNext(e -> {
System.out.println(e);
}).doOnError(e -> e.printStackTrace()); System.out.println("begin to subscribe");
flux.subscribe(e -> {
System.out.println(e);
try {
TimeUnit.MINUTES.sleep(30);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
});
TimeUnit.MINUTES.sleep(30);
}

这个例子requested是Long.MAX_VALUE,但是由于subscribe的线程跟运行interval的线程一样,由于里头执行了sleep操作也导致interval的调度也跟着阻塞住了。

实例2

    public static void main(String[] args) throws InterruptedException {
Flux<Long> flux = Flux.interval(Duration.ofMillis(1))
.doOnNext(e -> {
System.out.println(e);
})
//NOTE 这里request prefetch=256个
.publishOn(Schedulers.newElastic("publish-thread"))
.doOnError(e -> e.printStackTrace()); System.out.println("begin to subscribe");
AtomicInteger count = new AtomicInteger(0);
//NOTE 得有subscribe才能触发request
flux.subscribe(e -> {
LOGGER.info("receive:{}",e);
try {
//NOTE 使用publishOn将subscribe与interval的线程分开
if(count.get() == 0){
TimeUnit.MINUTES.sleep(2);
}
count.incrementAndGet();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
});
TimeUnit.MINUTES.sleep(30);
}

使用publishOn将subscriber线程与interval线程隔离,使其sleep不阻塞interval
这里publishOn隐含了一个prefetch参数,默认是Queues.SMALL_BUFFER_SIZE即Math.max(16,Integer.parseInt(System.getProperty("reactor.bufferSize.small", "256")));

	public final Flux<T> publishOn(Scheduler scheduler) {
return publishOn(scheduler, Queues.SMALL_BUFFER_SIZE);
} final Flux<T> publishOn(Scheduler scheduler, boolean delayError, int prefetch, int lowTide) {
if (this instanceof Callable) {
if (this instanceof Fuseable.ScalarCallable) {
@SuppressWarnings("unchecked")
Fuseable.ScalarCallable<T> s = (Fuseable.ScalarCallable<T>) this;
try {
return onAssembly(new FluxSubscribeOnValue<>(s.call(), scheduler));
}
catch (Exception e) {
//leave FluxSubscribeOnCallable defer exception call
}
}
@SuppressWarnings("unchecked")
Callable<T> c = (Callable<T>)this;
return onAssembly(new FluxSubscribeOnCallable<>(c, scheduler));
} return onAssembly(new FluxPublishOn<>(this, scheduler, delayError, prefetch, lowTide, Queues.get(prefetch)));
}

这里使用Queues.get(prefetch)创建一个间接的队列来盛放元素

这个实例最后输出

//......
21:06:03.108 [publish-thread-2] INFO com.example.demo.FluxTest - receive:254
21:06:03.108 [publish-thread-2] INFO com.example.demo.FluxTest - receive:255
reactor.core.Exceptions$OverflowException: Could not emit tick 256 due to lack of requests (interval doesn't support small downstream requests that replenish slower than the ticks)
at reactor.core.Exceptions.failWithOverflow(Exceptions.java:215)
at reactor.core.publisher.FluxInterval$IntervalRunnable.run(FluxInterval.java:121)
at reactor.core.scheduler.PeriodicWorkerTask.call(PeriodicWorkerTask.java:59)
at reactor.core.scheduler.PeriodicWorkerTask.run(PeriodicWorkerTask.java:73)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

由于第一次request默认是256,之后在发射256个元素之后,subscriber没有跟上,导致interval的worker被cancel掉了,于是后续消费完256个元素之后,紧挨着就是OverflowException这个异常

小结

reactor本身并不依赖线程,只有interval,delayElements等方法才会创建线程。而reactor本身是观察者设计模式的扩展,采用push+backpressure模式,一开始调用subscribe方法就触发request N请求推送数据,之后publisher就onNext推送数据,直到complete或cancel。实例1是因为线程阻塞导致interval的onNext阻塞,实例2是interval被cancel掉导致flux关闭。

转载于:https://my.oschina.net/go4it/blog/1622063

FluxInterval实例及解析的更多相关文章

  1. 大数据应用日志采集之Scribe演示实例完全解析

    大数据应用日志采集之Scribe演示实例完全解析 引子: Scribe是Facebook开源的日志收集系统,在Facebook内部已经得到大量的应用.它能够从各种日志源上收集日志,存储到一个中央存储系 ...

  2. Java解析XML文档(简单实例)——dom解析xml

      一.前言 用Java解析XML文档,最常用的有两种方法:使用基于事件的XML简单API(Simple API for XML)称为SAX和基于树和节点的文档对象模型(Document Object ...

  3. boot.img格式文件拆解实例结构解析

    以msm8226为例,讲解android源码编译生成boot.img的结构.boot.img包括boot.img header.kernel以及ramdisk文件系统.下面是对boot.img的结构进 ...

  4. 【SpringMVC架构】SpringMVC入门实例,解析工作原理(二)

    上篇博文,我们简单的介绍了什么是SpringMVC.这篇博文.我们搭建一个简单SpringMVC的环境,使用非注解形式实现一个HelloWorld实例,从简单入手,逐步深入. 环境准备 我们须要有主要 ...

  5. 邵国际: C 语言对象化设计实例 —— 命令解析器

    本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者: 邵国际 来源: 微信公众号linux阅码场(id: linuxdev) 内容简介 单片机工程师常常疑惑为什么 ...

  6. SwiftUI 官方画图实例详细解析

    前言 在前面几篇关于SwiftUI的文章中,我们用一个具体的基本项目Demo来学习了下SwiftUI,里面包含了常见的一些控件使用以及数据处理和地图等等,有兴趣的小伙伴可以去翻翻以前的文章,在前面总结 ...

  7. Argo 安装和 workflow 实例配置文件解析

    一.Argo 安装配置 1.1 Argo 安装 $ kubectl create ns argo $ kubectl apply -n argo -f https://raw.githubuserco ...

  8. 第一个SpringMVC实例和解析(HelloSpringMVC)

    1. 开发步骤: (1)增加Spring支持 下载Spring安装包和其依赖的commons-logging.jar,复制到项目Web应用的lib文件夹(WebRoot/WEB-INF/lib): S ...

  9. SQL Server的实例恢复解析

    同Oracle一样,SQL Server在非一致性关闭的时候也会进行实例恢复(Instance Recovery),本文根据stack overflow的文章介绍一些SQL Server实例恢复的知识 ...

随机推荐

  1. 浅谈 PCA与SVD

    前言 在用数据对模型进行训练时,通常会遇到维度过高,也就是数据的特征太多的问题,有时特征之间还存在一定的相关性,这时如果还使用原数据训练模型,模型的精度会大大下降,因此要降低数据的维度,同时新数据的特 ...

  2. 关于Tkinter的介绍

    Introduction to Tkinter 原英文教程地址zetcode.com In this part of the Tkinter tutorial, we introduce the Tk ...

  3. JAVA中String和StringBuilder类的特点及使用

    转自:https://www.imooc.com/code/2202 仅做个人学习记录之用,侵删! 什么是 Java 中的字符串 在 Java 中,字符串被作为 String 类型的对象处理. Str ...

  4. istream_iterator && istream_iteratorbuf

    注意 读字符时, std::istream_iterator 默认跳过空白符(除非用 std::noskipws 或等价物禁用,而 std::istreambuf_iterator 不跳过.另外, s ...

  5. transaction 用tx事务 测试时 报错:Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/mvc]

    Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/sc ...

  6. 【FreeMarker】【程序开发】数据模型,对象包装

    [FreeMarker][程序开发]数据模型,对象包装 分类: Java.FreeMarker2014-10-25 18:49 413人阅读 评论(0) 收藏 举报 FreeMarker   目录(? ...

  7. Candy Distribution

    Kids like candies, so much that they start beating each other if the candies are not fairly distribu ...

  8. 计算机系统基础学习笔记(1)-基本GCC,objdump,GBD命令的使用

    基本GCC命令的使用 GCC是一套由GNU项目开发的编程语言编译器,可处理C语言. C++.Fortran.Pascal.Objective-C.Java等等.GCC通常是 跨平台软件的编译器首选.g ...

  9. asp.net core webapi Session 内存缓存

    Startup.cs文件中的ConfigureServices方法配置: #region Session内存缓存 services.Configure<CookiePolicyOptions&g ...

  10. sudo -s 命令 [oh-my-zsh] 提示检测到不安全目录

    运行sudo -s 命令时,[oh-my-zsh] 冒出下面一大堆提示: [oh-my-zsh] Insecure completion-dependent directories detected: ...