Reactor学习:三、订阅

声明:


一、subscribe方法

注意:所有的操作只有在订阅的那一刻才开始进行!!!
subscribe方法有两种常用的形式:

  • Disposable subscribe(@Nullable Consumer<? super T> consumer, @Nullable Consumer<? super Throwable> errorConsumer, @Nullable Runnable completeConsumer, @Nullable Consumer<? super Subscription> subscriptionConsumer)
    这四个参数的意义如下:
    1)consumer:收到一个数据时的回调
    2)errorConsumer:上游报告出现错误信号时的回调
    3)completeConsumer:上游报告完成信号时的回调
    4)subscriptionConsumer:提供一个Subscription类型的对象,你可以使用他对上游流量进行反馈控制
    Disposable返回类型:使用Disposable#dispose方法可以取消订阅行为,在FluxMono中,取消意味着告知数据源不再生产数据,但这种取消行为并不一定是及时的,也许数据源产生数据非常快,在接收到取消信号之前就完成所有数据的生成。
    Disposable有一个工具类Disposables,它主要提供了Disposable.SwapDisposable.Composite两种包装类的工厂方法,前者允许你对一个Disposable进行替换(不取消容器中当前的Disposable,仅作替换)以及更新(取消容器中当前的Disposable,并替换为新的Dsiposable)操作,后者允许你一次性控制多个Disposable
  • void subscribe(Subscriber<? super T> actual)
    Subscriber是一个订阅器,里面包含了onSubscribeonNextonErroronError四种方法,使用它们可以更加方便的控制订阅的操作。当然实际中使用的还是BaseSubscriber,这个类继承了Subscriber,但是它是抽象的,无法直接实例化。使用时直接使用匿名类进行实例化就好,这样可以很好避免一个BaseSubscriber实例同时作为两个不同订阅操作的订阅器所带来的异常,因为根据Reactive Stream规则来说,一个订阅器中的onNext方法只能被时序调用,而不同同时调用。
    你可以看下面这个例子:
public class CustomSubsriber<T> extends BaseSubscriber<T> {
    @Override
    protected void hookOnSubscribe(Subscription subscription) {
        System.out.println("subscribed");
        request(1);
    }

    @Override
    protected void hookOnNext(T value) {
        System.out.println("get value:" + value);
        LockSupport.parkNanos(Duration.ofSeconds(1).toNanos());
        request(1);
    }

    @Override
    protected void hookOnComplete() {
        System.out.println("completed");
    }
}

二、背压——流控

Reactor中,下游想要控制上游的流速,是通过request来实现的,request的总数代表着下游当前的需求量,比如subscriber.request(1)就代表下游需要一个数据,多了不要。如果你把request的值设为Long.MAX_VALUE,则它意味着下游可以接收无限制的数据,比如Mono#blockFlux#blockFirstFlux#blockLast就默认无限制接收数据。对了,这三个方法也是一种订阅操作,使用它们会激活整个订阅过程,Mono#blockFlux#blockFirst均表示接收流中第一个数据并返回,如果在等待中接收到了完成信号则返回null,同理Flux#blockLast表示只接收最后一个数据,其会一直等待完成信号的到来。
值得注意的是:request并不一定恒定的,它可能会被整个上游中的某个操作修改,比如stringFlux.buffer(3).subscriber(null,null,null,sub -> {sub.request(2)})中,buffer(3)会将request进一步修改为6,因为sub.request(2)的2个请求是请求buffer(3)的输出结果的,而一个buffer(3)结果需要其上游的3个数据,故request被更改为了6

  • limitRequest也是一种流控操作,它接收一个参数作为“最大请求量”,如果下游总的请求量并没有到达上限,那么一切照常,否则,在到达那一刻时,改操作将会给上游发出取消信号,并给下游发出完成信号
  • limitRate将请求分组,比如下游请求100个request(100),那么limitRate(10)将会把请求分为10个request(10),每完成一个request(10)则自动发出下一个request(10)

三、异步

Reactor中,如果不特别指定异步操作的话,那么整个流的发生到订阅过程全部会执行在subscribe那个线程中。所以最简单的异步使用Reactor的方法就是新建一个线程,并在其中执行subscribe,比如:

public static void main(String[] args) throws InterruptedException {
  final Mono<String> mono = Mono.just("hello "); 

  Thread t = new Thread(() -> mono
      .map(msg -> msg + "thread ")
      .subscribe(v -> 
          System.out.println(v + Thread.currentThread().getName()) 
      )
  )
  t.start();
  t.join();
}

其结果为:

hello thread Thread-0

当然Reactor其实提供更加简便的异步操作方式,其中比较常用的就是publishOnSubscribeOn两个方法了,这两个方法都需要一个Scheduler类型的参数,它控制着操作的执行模式以及执行执行位置,单从表现上来看,倒是有点像ExecutorService。创建Scheduler你需要使用到Schedulers工厂类,里面定义了许多不同类型的Scheduler
1)Schedulers.immediate():直接在当前线程中立刻执行
2)Schedulers.single()/newSingle():提供一个单线程线程池以供操作,前者是一个固定的定义好的单线程线程池,后者你可以使用它来创建新的单线程线程池
3)Schedulers.elastic():相当于提供了一个CachedThreadPool
4)Schedulers.parallel():相当于提供了一个和Cpu核心数一样多的核心线程数的线程池
5)Schedulers.fromExecutorService():从现有的ExecutorService中引入

  • subscribeOn
    subscribeOn会将整个流(包括数据源生成)放置在指定线程上执行(具体由哪个线程执行由前面所说的Scheduler来控制),注意,无论subscribeOn放在哪,它都将影响整个流,可以有多个subscribeOn,但是只有第一个会生效
  • publishOn
    publishOn会将其后的操作搬移到指定线程上执行,注意,publishOn优先级比subscribeOn高,而且多个publishOn都各自对其后的操作会有影响。

看个例子:

final Flux<String> flux = Flux
        .range(1, 2)
        .map(i -> {
            System.out.println("map1:"+Thread.currentThread().getName());
            LockSupport.parkNanos(Duration.ofSeconds(1).toNanos());
            return 10 + i;
        })
        .subscribeOn(s)
        .map(i -> {
            System.out.println("map2:"+Thread.currentThread().getName());
            LockSupport.parkNanos(Duration.ofSeconds(2).toNanos());
            return "value " + i;
        }).publishOn(Schedulers.single())
        .map(i -> {
            System.out.println("map3:"+Thread.currentThread().getName());
            return "mtk:"+i;
        });

new Thread(() -> flux.subscribe(System.out::println)).start();

结果如下:

map1:parallel-scheduler-1
map2:parallel-scheduler-1
map1:parallel-scheduler-1
map3:single-1
mtk:value 11
map2:parallel-scheduler-1
map3:single-1
mtk:value 12

从结果中我们可以发现整个流在没有运行在subscribe方法调用时所在的线程中,因为有subscribeOn的缘故,整个流运行在parallel-scheduler-1线程中,但是在map3操作前,有个publishOn,其使得其后的操作运行在了single-1线程中


参考文档:
[1] Reactor api doc
[2] Reactor reference doc

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260