RxJava——并行编程

RxJava并行编程

一、RxJava并行操作

1.1、借助flatMap实现并行

在RxJava中可以借助flatMap操作符来实现类似于Java8的并行执行效果。

java 复制代码
@Test
public void testFlatMap() {
    Observable.range(1, 100)
            .flatMap(i -> {
                return Observable.just(i)
                        .subscribeOn(Schedulers.computation())
                        .map(e -> {
                            return e.toString();
                        });
            })
            .subscribe(data -> {
                System.out.println(Thread.currentThread().getName() + " : " + data);
            });


    try {
        Thread.sleep(3000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

RxComputationThreadPool-5 : 5
RxComputationThreadPool-7 : 7
RxComputationThreadPool-8 : 8
RxComputationThreadPool-9 : 9
RxComputationThreadPool-10 : 10
RxComputationThreadPool-11 : 11
RxComputationThreadPool-12 : 12
RxComputationThreadPool-5 : 17
RxComputationThreadPool-7 : 19
RxComputationThreadPool-10 : 22
RxComputationThreadPool-12 : 24
RxComputationThreadPool-10 : 34
RxComputationThreadPool-3 : 3
...

flatMap操作符的原理是将这个Observable转化为多个以原Observable发射的数据作为源数据Observable,然后再将这多个Observable发射的数据整合发射出来。需要注意的是,最后的顺序可能会交错地发射出来。

flatMap会对原始Observable发射的每一项数据执行变换操作。在这里,生成的每个Observable使用线程池(指定了computation作为Scheduler)并发地执行。当然,我们还可以使用ExecutorService来创建一个Scheduler,对刚才的代码稍微做一些改动。

java 复制代码
@Test
public void testFlatMap2() {
    int threadNum = Runtime.getRuntime().availableProcessors() + 1;
    ExecutorService executor = Executors.newFixedThreadPool(threadNum);
    final Scheduler scheduler = Schedulers.from(executor);
    Observable.range(1, 100)
            .flatMap(i -> {
                return Observable.just(i)
                        .subscribeOn(scheduler)
                        .map(e -> {
                            return e.toString();
                        });
            })
            .subscribe(data -> {
                System.out.println(Thread.currentThread().getName() + " : " + data);
            });


    try {
        Thread.sleep(3000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

pool-1-thread-3 : 3
pool-1-thread-8 : 8
pool-1-thread-9 : 9
pool-1-thread-10 : 10
pool-1-thread-11 : 11
pool-1-thread-12 : 12
pool-1-thread-13 : 13
pool-1-thread-13 : 16
pool-1-thread-13 : 17
...

需要补充的是:当完成所有的操作之后,ExecutorService需要执行shutdownO来关闭ExecutorService。我们可以使用doFinally操作符来执行shutdown()。

doFinally操作符可以在onError或者onComplete之后调用指定的操作,或由下游处理。增加了doFinally操作符之后,代码是这样的。

java 复制代码
@Test
public void testFlatMap2() {
    int threadNum = Runtime.getRuntime().availableProcessors() + 1;
    ExecutorService executor = Executors.newFixedThreadPool(threadNum);
    final Scheduler scheduler = Schedulers.from(executor);
    Observable.range(1, 100)
            .flatMap(i -> {
                return Observable.just(i)
                        .subscribeOn(scheduler)
                        .map(e -> {
                            return e.toString();
                        });
            })
            .doFinally(() -> {
                executor.shutdown();
            })
            .subscribe(data -> {
                System.out.println(Thread.currentThread().getName() + " : " + data);
            });


    try {
        Thread.sleep(3000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

1.2、Round-Robin算法实现并行

Round-Robin算法是最简单的一种负载均衡算法。它的原理是把来自用户的请求轮流分配给内部的服务器:从服务器1开始,直到服务器N,然后重新开始循环,也被称为哈希取模法,是非常常用的数据分片方法。Round-Robin算法的优点是简洁,它无须记录当前所有连接的状态,所以是一种无状态调度。

通过Round-Robin算法把数据按线程数分组,例如分成5组,每组个数相同,一起发送处理。这样做的目的是可以减少Observable的创建,从而节省系统资源,但是会增加处理时间。Round-Robin算法可以看成是对时间和空间的综合考虑。

java 复制代码
@Test
public void testRR() {
    final AtomicInteger batch = new AtomicInteger(0);

    Observable.range(1, 100)
            .groupBy(i -> {
                return batch.getAndIncrement() % 5;
            })
            .flatMap(new Function<GroupedObservable<Integer, Integer>, ObservableSource<?>>() {
                @Override
                public ObservableSource<?> apply(GroupedObservable<Integer, Integer> integerIntegerGroupedObservable) throws Exception {
                    return integerIntegerGroupedObservable.observeOn(Schedulers.io())
                            .map(i -> i.toString());
                }
            })
            .subscribe(data -> {
                System.out.println(Thread.currentThread().getName() + " : " + data);
            });

    try {
        Thread.sleep(3000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

RxCachedThreadScheduler-1 : 1
RxCachedThreadScheduler-2 : 2
RxCachedThreadScheduler-3 : 3
RxCachedThreadScheduler-4 : 4
RxCachedThreadScheduler-5 : 5
RxCachedThreadScheduler-5 : 6
RxCachedThreadScheduler-5 : 11
RxCachedThreadScheduler-5 : 16
RxCachedThreadScheduler-5 : 7
...

二、ParallelFlowable

2.1、简介

RxJava2.0.5版本新增了ParallelFlowable API,它允许并行地执行一些操作符,例如map、filter、concatMap、flatMap、collect、reduce等。

ParallelFlowable是并行的Flowable版本,并不是新增的被观察者类型。在ParallelFlowable中,很多典型的操作符(如take、skip等)是不可用的。

在RxJava中并没有ParallelObservable,因为在RxJava2之后,Observable不再支持背压。然而在并行处理中背压是必不可少的,否则会淹没在并行操作符的内部队列中。同理,也不存在ParallelSingle、ParallelCompletable以及ParallelMaybe。

2.2、ParallelFlowable实现并行

类似于Java8的并行流,在相应的操作符上调用Flowable的parallel()就会返回ParallelFlowable。

java 复制代码
@Test
public void testPrarallelFlowable() {
    ParallelFlowable parallelFlowable = Flowable.range(1, 100).parallel();

    parallelFlowable
            .runOn(Schedulers.io())
            .map(i -> i.toString())
            .sequential()
            .subscribe(data -> System.out.println(Thread.currentThread().getName() + " : " + data));

    try {
        Thread.sleep(3000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

RxCachedThreadScheduler-2 : 2
RxCachedThreadScheduler-2 : 1
RxCachedThreadScheduler-2 : 3
RxCachedThreadScheduler-2 : 4
RxCachedThreadScheduler-2 : 5
RxCachedThreadScheduler-2 : 6
RxCachedThreadScheduler-2 : 7
...

其中,parallel()调用了ParallelFlowable.from(@NonNull Publisher<?extends T>source)

ParallelFlowable的fromO方法是通过Publisher并以循环的方式在多个"轨道"(CPU数)上消费它的。

默认情况下,并行级别被设置为可用CPU的数量(Runtime.getRuntime()

availableProcessors(),并且顺序源的预取量设置为Flowable.bufferSize()。两者都可以通过重载parallelO方法来指定。

最后,如果己经使用了必要的并行操作,则可以通过ParallelFlowable.sequentialO操作符返回到顺序流。

2.2、ParallelFlowable与Scheduler

ParallelFlowable遵循与Flowable相同的异步原理,因此parallelO本身并不引入顺序源的异步消耗,只准备并行流,但是可以通过runOn(Scheduler)操作符定义异步。这点与Flowable有很大不同,Flowable使用subscribeOn、observeOn操作符。

java 复制代码
ParallelFlowable<Integer> psource = source.runOn(Schedulers.io());

runOn()可以指定prefetch的数量。

2.3、ParallelFlowable操作符

并非所有的顺序操作在并行世界中都是有意义的。目前ParallelFlowable只支持如下操作:

  • map
  • filter
  • flatMap
  • concatMap
  • reduce
  • collect
  • sorted
  • toSortedList
  • compose
  • fromArray
  • doOnCancel
  • doOnError
  • doOnComplete
  • doOnNext
  • doAfterNext
  • doOnSubscribe
  • doAfterTerminated,doOnRequest

这些ParallelFlowable可用的操作符,使用方法与Flowable中的用法一样。

相关推荐
小飞学编程...3 小时前
【Java相关八股文(二)】
android·java·开发语言
FunW1n3 小时前
Android Studio与Hook模块开发相关问题及实现方案梳理
android·ide·android studio
技术传感器4 小时前
解剖“数字孪生“:语义层定义世界,动力层驱动世界
android·运维·服务器
lxysbly4 小时前
n64模拟器安卓版官网
android
奔跑吧 android4 小时前
【车载Audio】【AudioHal 03】【深入解析 Android 音频策略:onNewAudioModulesAvailableInt 的全链路探索】
android·aosp15·音频策略·audiohal·车载audio
hinewcc4 小时前
Android SELinux权限
android
CrystalShaw5 小时前
节前最后一天mark:Perfetto
android
我命由我123455 小时前
Kotlin 面向对象 - 匿名内部类、匿名内部类简化
android·java·开发语言·java-ee·kotlin·android studio·android jetpack
catchadmin5 小时前
“Fatal error: require(): Failed opening required...” 以及如何彻底避免它再次出现
android·ide·android studio