RxJava学习 - 10. Concurrency and Parallelization

10年来,并发需求增长迅速,已经是Java开发者的必要技能。Concurrency(也叫多线程)实质上是多任务,需要同一时间执行几个处理。如果你想充分利用硬件的运算能力(电话、服务器、笔记本电脑或者桌面计算机),需要学习怎样才能使用多线程和并发。RxJava让并发更容易更安全。

Introducing RxJava concurrency

默认地,Observables在及时(immediate)线程(就是声明Observer的线程)上执行工作。前面的很多例子,就是启动main()方法的线程。
但是也有一些例子,不是所有的Observables都在immediate线程上执行。想想那些使用Observable.interval()的代码:

import io.reactivex.Observable;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable.interval(1, TimeUnit.SECONDS)
                .map(i -> i + " Mississippi")
                .subscribe(System.out::println);
        
        sleep(5000);
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上面的Observable实际上在另一个线程上执行。main线程启动Observable.interval(),但是不会等它完成,因为它用了另一个线程,程序现在包含两个线程。如果main线程调用sleep()方法,main()方法结束,程序退出前,有机会发射。
通常,只有在长时间运行或者计算密集的处理才需要并发。下面的例子,增加一个叫做intenseCalculation()的方法,仿真一个长时间运行的程序。它会简单地接受任何值,然后sleep0-3秒,然后返回相同的值。让线程休眠,或者暂停它 ,可以模仿繁忙工作的线程:

public static <T> T intenseCalculation(T value) {
    sleep(ThreadLocalRandom.current().nextInt(3000));
    return value;
}

让我们增加两个Observables,再增加两个Observers订阅他们。每个map方法调用intenseCalculation()方法让他们变慢:

import io.reactivex.Observable;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
                .map(s -> intenseCalculation((s)))
                .subscribe(System.out::println);
        Observable.range(1,6)
                .map(s -> intenseCalculation((s)))
                .subscribe(System.out::println);
    }

    public static <T> T intenseCalculation(T value) {
        sleep(ThreadLocalRandom.current().nextInt(3000));
        return value;
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出是

Alpha
Beta
Gamma
Delta
Epsilon
1
2
3
4
5
6

可以看到,Observables发射得很慢。而且,第一个发射完,第二个才开始发射。如果两个能同时发射,而不是一个等另一个结束,就可以执行得快一些。
我们可以使用subscribeOn()实现,它可以让源在特定的Scheduler上发射。让我们使用Schedulers.computation(),它会使用固定数量的线程池。
它为每个Observer提供一个线程发射emissions。调用了onComplete(),该线程就被Scheduler回收,等待重用:

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
                .subscribeOn(Schedulers.computation())
                .map(s -> intenseCalculation((s)))
                .subscribe(System.out::println);
        Observable.range(1,6)
                .subscribeOn(Schedulers.computation())
                .map(s -> intenseCalculation((s)))
                .subscribe(System.out::println);
        sleep(20000);
    }

    public static <T> T intenseCalculation(T value) {
        sleep(ThreadLocalRandom.current().nextInt(3000));
        return value;
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

RxJava的operators能安全地在不同的线程上工作。每个组合多个Observables的operators和工厂,比如merge()个zip(),能安全地组合不同的线程上发射的emissions:

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable<String> source1 =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
                        .subscribeOn(Schedulers.computation())
                        .map(s -> intenseCalculation((s)));
        Observable<Integer> source2 =
                Observable.range(1, 6)
                        .subscribeOn(Schedulers.computation())
                        .map(s -> intenseCalculation((s)));
        Observable.zip(source1, source2, (s, i) -> s + "-" + i)
                .subscribe(System.out::println);
        sleep(20000);
    }

    public static <T> T intenseCalculation(T value) {
        sleep(ThreadLocalRandom.current().nextInt(3000));
        return value;
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Keeping an application alive

之前,我们使用sleep()方法防止并发的响应式程序过早退出。如果你使用Android、JavaFX或其他拥有自己的non-daemon线程的框架,这不是一个问题,因为程序会保持运行状态。但是,如果简单地使用main()方法,想启动长时间运行或者无限Observables的程序,你不得不长时间保持main线程活着。
一个办法是让线程sleep下去:

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable.interval(1, TimeUnit.SECONDS)
                .map(l -> intenseCalculation((l)))
                .subscribe(System.out::println);
        sleep(Long.MAX_VALUE);
    }

    public static <T> T intenseCalculation(T value) {
        sleep(ThreadLocalRandom.current().nextInt(3000));
        return value;
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

另一个办法是,Brian Goetz在Java Concurrency in Practice书里讨论过的,使用CountDownLatch等待订阅完成。RxJava有一个容易的办法是使用blocking operators,它可以用来停止声明的线程和等待emissions。通常,它用于单元测试,在生产中,如果使用不当,会引起antipatterns。使用blocking operator,可以让包含有限的Observable的程序长期存活。看下面的例子,使用blockingSubscribe()代替subscribe(),会在程序退出之前,等待调用onComplete():

扫描二维码关注公众号,回复: 4468764 查看本文章
import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
                .subscribeOn(Schedulers.computation())
                .map(s -> intenseCalculation((s)))
                .blockingSubscribe(System.out::println,
                        Throwable::printStackTrace,
                        () -> System.out.println("Done!"));
    }

    public static <T> T intenseCalculation(T value) {
        sleep(ThreadLocalRandom.current().nextInt(3000));
        return value;
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Understanding Schedulers

一些线程池有固定数量的线程,可以使用ExecutorService返回一个线程池。RxJava有它自己的并发抽象,叫Scheduler。像ExecutorService或者actor系统那样,它定义方法和规则。
在Schedulers静态工厂类里可以找到很多默认的Scheduler实现。对于一个给定的Observer,一个Scheduler会从线程池提供一个线程,用来发射emissions。
调用onComplete(),operation被处置,线程被回收。

Computation

Schedulers.computation(),会维护一个固定数量(根据处理器数量)的线程池。计算任务(比如数学、算法和复杂逻辑)可以充分利用核心。
如果你不确信有会并发执行多少任务,或者不确信应该使用哪个Scheduler,最好使用默认的那个。

IO

读写数据库、web请求、磁盘存储那样的IO任务较少使用CPU,经常有等待数据被发送或者返回的空闲时间。此时应该使用Schedulers.io()。
它会维护的线程数和任务数一样多,并且根据需要的数量动态增长、缓存和减少。比如,你可以用Schedulers.io()执行使用RxJava-JDBC
执行SQL的操作:

Database db = Database.from(conn);
Observable<String> customerNames =
    db.select("SELECT NAME FROM CUSTOMER")
            .getAs(String.class)
            .subscribeOn(Schedulers.io());

但是你要小心,根据经验,每个订阅可能返回一个新的线程。

New thread

Schedulers.newThread()工厂返回的Scheduler不使用线程池。它会为每个Observer增加一个新线程,运行结束以后销毁该线程:

Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
        .subscribeOn(Schedulers.newThread());

Single

当你想在一个线程上顺序执行任务,可以调用Schedulers.single()。一个单线程的实现支持着它。非线程安全的操作适合使用它:

Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
        .subscribeOn(Schedulers.single());

Trampoline

Schedulers.trampoline()是个有趣的Scheduler。我们很少使用它,它主要用于RxJava的内部实现。它很像立即线程上的默认调度器,但是它会防止递归调度(在同一个线程上,一个任务调度另一个任务)。它不会导致堆栈溢出错误,而是完成了当前任务再执行新任务。

ExecutorService

可以使用ExecutorService建立一个Scheduler。比如,我们想增加一个使用20个线程的Scheduler:

import io.reactivex.Observable;
import io.reactivex.Scheduler;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Launcher {
    public static void main(String[] args) {
        int numberOfThreads = 20;
        ExecutorService executor =
                Executors.newFixedThreadPool(numberOfThreads);
        Scheduler scheduler = Schedulers.from(executor);
        Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
                .subscribeOn(scheduler)
                .doFinally(executor::shutdown)
                .subscribe(System.out::println);        
    }
}

ExecutorService会让你的程序一直活着,

Starting and shutting down Schedulers

每个默认Scheduler在你第一次调用它的时候才实例化。可以在任何时候使用shutdown()方法处置computation()、io()、newThread()、single()和trampoline() Schedulers,也可以使用Schedulers.shutdown()把他们全部处置掉。调用shutdown(),会停掉他们的线程,禁止新任务,调用其他方法会产生错误。也可以调用他们的start()方法,或者Schedulersers.start(),重新初始化Schedulers,然后可以重新接受任务。

Understanding subscribeOn()

subscribeOn()建议上游的源Observable,可以使用哪个Scheduler,以及怎样在其中的线程上执行。如果源不打算拴在特定的Scheduler上,它会使用你指定的Scheduler。然后,它使用这个线程发射emissions,一直发给最后的Observer(除非你又增加了observeOn()调用)。可以把subscribeOn()放在Observable链的任何地方。
看下面的例子,subscribeOn()放在哪儿,效果都是一样的(为清楚起见,subscribeOn()最好靠近源):

Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
        .subscribeOn(Schedulers.computation()) //preferred
        .map(String::length)
        .filter(i -> i > 5)
        .subscribe(System.out::println);
Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
        .map(String::length)
        .subscribeOn(Schedulers.computation())
        .filter(i -> i > 5)
        .subscribe(System.out::println);
Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
        .map(String::length)
        .filter(i -> i > 5)
        .subscribeOn(Schedulers.computation())
        .subscribe(System.out::println);

同一个Observable有多个Observers,使用subscribeOn()会导致每个都有自己的线程(或者如果没有有效的线程的时候,等待一个有效的线程)。
你可以调用Thread.currentThread().getName()打印执行线程的名字:

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable<Integer> lengths =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
                        .subscribeOn(Schedulers.computation())
                        .map(s -> intenseCalculation((s)))
                        .map(String::length);
        lengths.subscribe(i ->
                System.out.println("Received " + i + " on thread " + Thread.currentThread().getName()));
        lengths.subscribe(i ->
                System.out.println("Received " + i + " on thread " + Thread.currentThread().getName()));
        sleep(10000);
    }

    public static <T> T intenseCalculation(T value) {
        sleep(ThreadLocalRandom.current().nextInt(3000));
        return value;
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

一个Observer使用的线程名是RxComputationThreadPool-2,另一个使用的是RxComputationThreadPool-1。
如果我们想只使用一个线程,可以multicast。仅仅确保subscribeOn()在multicast之前:

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable<Integer> lengths =
                Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
                        .subscribeOn(Schedulers.computation())
                        .map(s -> intenseCalculation((s)))
                        .map(String::length)
                        .publish()
                        .autoConnect(2);
        lengths.subscribe(i ->
                System.out.println("Received " + i + " on thread " +
                        Thread.currentThread().getName()));
        lengths.subscribe(i ->
                System.out.println("Received " + i + " on thread " +
                        Thread.currentThread().getName()));
        sleep(10000);
    }

    public static <T> T intenseCalculation(T value) {
        sleep(ThreadLocalRandom.current().nextInt(3000));
        return value;
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

大多数Observable工厂,比如Observable.fromIterable()和Observable.just(),会在subscribeOn()指定的Scheduler上发射元素。Observable.fromCallable()和Observable.defer()这样的工厂,当使用subscribeOn()的时候,这些源的初始化也运行在指定的Scheduler上。比如,如果你使用Observable.fromCallable()等待一个URL的响应,你实际上可以在IO Scheduler上工作,这样main线程不会被阻塞:

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.net.URL;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable.fromCallable(() ->
                getResponse("https://api.github.com/users/thomasnield/starred")
        ).subscribeOn(Schedulers.io())
                .subscribe(System.out::println);
        sleep(10000);        
    }
    
    private static String getResponse(String path) {
        try {
            return new Scanner(new URL(path).openStream(), "UTF-8").useDelimiter("\\A").next();
        } catch (Exception e) {
            return e.getMessage();
        }
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }        
}

Nuances of subscribeOn()

subscribeOn()对某些源没影响(而且在运行结束前,保持一个不必要的standby线程)。这可能是因为这些Observables已经使用了指定的Scheduler,如果你想改变它,可以提供一个Scheduler参数。比如,Observable.interval()使用Schedulers.computation(),忽略你指定的任何subscribeOn()。但是,你可以提供第三个参数指定一个不同的Scheduler:

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable.interval(1, TimeUnit.SECONDS, Schedulers.newThread())
                .subscribe(i -> System.out.println("Received " + i +
                        " on thread " + Thread.currentThread().getName()));
        sleep(5000);
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这也会带来另一个问题:如果你在一个给定的Observable链上有多个subscribeOn()调用,最前面的那个(或者最接近源的),会胜利,导致后面的没有影响。如果你使用Schedulers.computation()调用subscribeOn(),然后使用Schedulers.io()调用subscribeOn(),会使用Schedulers.computation():

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable.just("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
                .subscribeOn(Schedulers.computation())
                .filter(s -> s.length() == 5)
                .subscribeOn(Schedulers.io())
                .subscribe(i -> System.out.println("Received " + i +
                        " on thread " + Thread.currentThread().getName()));
        sleep(5000);
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Understanding observeOn()

如果subscribeOn()是Observable链上唯一的并发operation,Scheduler的线程会在整个Observable链上工作,沿着链,把源发射到最终的Observer。而observeOn(),会在链上的那个点,拦截emissions把他们切到一个不同的Scheduler,继续传递。
不像subscribeOn(),observeOn()的位置很重要。下面的例子,Observable发射一些以“/”分隔的字符串,用的是IO Scheduler。然后,使用computation Scheduler过滤剩下数字,求和:

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        //Happens on IO Scheduler
        Observable.just("WHISKEY/27653/TANGO", "6555/BRAVO", "232352/5675675/FOXTROT")
                .subscribeOn(Schedulers.io())
                .flatMap(s -> Observable.fromArray(s.split("/")))
                //Happens on Computation Scheduler
                .observeOn(Schedulers.computation())
                .filter(s -> s.matches("[0-9]+"))
                .map(Integer::valueOf)
                .reduce((total, next) -> total + next)
                .subscribe(i -> System.out.println("Received " + i + " on thread "
                        + Thread.currentThread().getName()));
        sleep(1000);
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

当然,这个例子不是计算密集型的,应该使用单线程。引入并发的开销并不合理,我们假装它要运行很长时间。
observeOn()的上游不受影响,下游受影响。
如果想读一个或者更多数据源,等待响应,你想使用Schedulers.io()处理了。一旦你有了这些数据,你可能想做计算,Scheduler.io()不再适合这个工作。你想使用几个线程完成这任务。因此,你使用observeOn()重定向到Schedulers.computation()。
你可以使用多个observeOn()。让我们继续前面的例子,把计算好的和写到磁盘文件。我们假装有很多数据要写到磁盘,那么应该使用IO Scheduler。
我们可以使用第二个observeOn()。我们再加点doOnNext()和doOnSuccess(),检查分别使用了什么线程:

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.TimeUnit;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;

public class Launcher {
    public static void main(String[] args) {
        //Happens on IO Scheduler
        Observable.just("WHISKEY/27653/TANGO", "6555/BRAVO", "232352/5675675/FOXTROT")
                .subscribeOn(Schedulers.io())
                .flatMap(s -> Observable.fromArray(s.split("/")))
                .doOnNext(s -> System.out.println("Split out " + s + " on thread "
                        + Thread.currentThread().getName()))
                //Happens on Computation Scheduler
                .observeOn(Schedulers.computation())
                .filter(s -> s.matches("[0-9]+"))
                .map(Integer::valueOf)
                .reduce((total, next) -> total + next)
                .doOnSuccess(i -> System.out.println("Calculated sum " + i + " on thread "
                        + Thread.currentThread().getName()))
                //Switch back to IO Scheduler
                .observeOn(Schedulers.io())
                .map(i -> i.toString())
                .doOnSuccess(s -> System.out.println("Writing " + s + " to file on thread "
                        + Thread.currentThread().getName()))
                .subscribe(s -> write(s,"/home/thomas/Desktop/output.txt"));
        sleep(1000);
    }

    public static void write(String text, String path) {
        BufferedWriter writer = null;
        try {
            //create a temporary file
            File file = new File(path);
            writer = new BufferedWriter(new FileWriter(file));
            writer.append(text);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                writer.close();
            } catch (Exception e) {
            }
        }
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Nuances of observeOn()

使用observeOn(),可能因为缺乏backpressure,导致性能问题。
一个Observable链包含两个operations集合。如果之间有任何observeOn(),不管用不用subscribeOn(),源的当前的emission传给Observer以后,才会把下一个往下传。
在Operation A和Operation B之间有一个observeOn()的时候,Operation A把一个emission传给observeOn(),它会立刻开始下一个emission,不等下游(包括Operation B和Observer)完成当前这个,这意味着源和Operation A的生产速度可以比Operation B和Observer的消费速度快。来不及处理的,放在observeOn()的队列里,等下游处理。如果你有很多emissions,可能会遇到内存问题。
当你有10万个或者更多emissions,你可以使用Flowable(支持backpressure),而不用Observable。backpressure让上游的生产速度慢下来,我们以后再讲。

Parallelization

Parallelization,也叫parallelism或者parallel computing。在RxJava里,表示一个给定的Observable,在同一时刻产生多个emissions。如果在给定的Observable链里,要处理1000个emissions,我们可能想让工作更快些,比如一次产生八个,而不是一个。回忆一下,Observable规定,必须顺序地发射emissions,不互相竞争。一次发射八个emissions,就是个灾难。幸好,RxJava提供了相应的工具。你不能在一个Observable里并发地发射,但是,你可以一次运行多个Observables,每个Observables有自己的线程发射元素。我们可以增加几个Observables,使用不同的schedulers,然后使用flatMap()。
下面的例子,有一个Observable通过intenseCalculation()发射10个整数,。我们打印时间来测量性能:

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Observable.range(1,10)
                .map(i -> intenseCalculation(i))
                .subscribe(i -> System.out.println("Received " + i + " "
                        + LocalTime.now()));
    }

    public static <T> T intenseCalculation(T value) {
        sleep(ThreadLocalRandom.current().nextInt(3000));
        return value;
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

怎样使用并行提高性能呢?

serialization(一次发射一个元素)需要在同一个Observable。flatMap()会合并多个Observables,衍生出每一个emission。在flatMap()内,让我们把每个emission包装进Observable.just(),调用使用Schedulers.computation()做参数的subscribeOn(),然后在map()内使用intenseCalculation():

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.time.LocalTime;
import java.util.concurrent.ThreadLocalRandom;

public class Launcher {
    public static void main(String[] args) {
        Observable.range(1,10)
                .flatMap(i -> Observable.just(i)
                        .subscribeOn(Schedulers.computation())
                        .map(i2 -> intenseCalculation(i2))
                )
                .subscribe(i -> System.out.println("Received " + i + " "
                        + LocalTime.now() + " on thread "
                        + Thread.currentThread().getName()));
        sleep(20000);        
    }

    public static <T> T intenseCalculation(T value) {
        sleep(ThreadLocalRandom.current().nextInt(3000));
        return value;
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
}

输出是

Received 6 09:23:51.067 on thread RxComputationThreadPool-6
Received 7 09:23:51.242 on thread RxComputationThreadPool-7
Received 5 09:23:51.546 on thread RxComputationThreadPool-5
Received 2 09:23:51.714 on thread RxComputationThreadPool-2
Received 1 09:23:52.689 on thread RxComputationThreadPool-1
Received 10 09:23:52.968 on thread RxComputationThreadPool-2
Received 4 09:23:53.226 on thread RxComputationThreadPool-4
Received 8 09:23:53.439 on thread RxComputationThreadPool-8
Received 3 09:23:54.030 on thread RxComputationThreadPool-3
Received 9 09:23:55.239 on thread RxComputationThreadPool-1

现在使用了八个线程。比单线程执行快了很多。
为每个emission增加一个Observable可能会增加不必要的开销。如果不想增加太多的Observable,可以把源分割成固定数量的Observables,然后使用flatMap()合并他们。比如,因为我有八个核,就使用八个Observables。
我们可以使用groupBy(),如果有八个核,就产生八个GroupedObservables。可以给每个emission分配0-7的key,根据key放到不同的流。GroupedObservables不一定受subscribeOn()的影响(除了缓存的emissions,都在源的线程上发射),但是需要observeOn()。我也可一使用io()或者newThread() scheduler,因为我已经把workers的数量限制成核的数量。

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.time.LocalTime;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;

public class Launcher {
    public static void main(String[] args) {
        int coreCount = Runtime.getRuntime().availableProcessors();
        AtomicInteger assigner = new AtomicInteger(0);
        Observable.range(1, 10)
                .groupBy(i -> assigner.incrementAndGet() % coreCount)
                .flatMap(grp -> grp.observeOn(Schedulers.io())
                        .map(i2 -> intenseCalculation(i2))
                )
                .subscribe(i -> System.out.println("Received " + i + " "
                        + LocalTime.now() + " on thread "
                        + Thread.currentThread().getName()));
        sleep(20000);     
    }

    public static <T> T intenseCalculation(T value) {
        sleep(ThreadLocalRandom.current().nextInt(3000));
        return value;
    }
    
    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
}

unsubscribeOn()

当处置一个Observable的时候,有时候代价很高,因为它依赖于外部资源。比如,如果使用RxJava-JDBC执行了数据库查询,停止和处置该Observable的代价是很昂贵的,因为需要关闭使用的JDBC资源。
下面是一个每秒发射的Observable。三秒以后停止main线程,然后调用dispose()关闭程序。我们使用doOnDispose()(由disposing线程执行)可以看到main线程确实执行了处置操作:

import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Disposable d = Observable.interval(1, TimeUnit.SECONDS)
                .doOnDispose(() -> System.out.println("Disposing on thread "
                        + Thread.currentThread().getName()))
                .subscribe(i -> System.out.println("Received " + i));
        sleep(3000);
        d.dispose();
        sleep(3000);
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

让我们使用unsubscribeOn(),在Schedulers.io()上取消订阅。把unsubscribeOn()放在想要影响的上游操作的后面:

import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.TimeUnit;

public class Launcher {
    public static void main(String[] args) {
        Disposable d = Observable.interval(1, TimeUnit.SECONDS)
                .doOnDispose(() -> System.out.println("Disposing on thread "
                        + Thread.currentThread().getName()))
                .unsubscribeOn(Schedulers.io())
                .subscribe(i -> System.out.println("Received " + i));
        sleep(3000);
        d.dispose();
        sleep(3000);
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

现在,通过IO Scheduler做处置了,线程名是RxCachedThreadScheduler-1。这样做,允许main线程开始disposal,然后继续运行,不等disposal结束。
和其他concurrency相似,不应该在这样轻量级的场合使用unsubscribeOn(),这样做引起不必要的负担。
如果你想把Observable链分成特定的部分,每个部分使用不用的Schedulers处置,可以调用多个unsubscribeOn()。每个unsubscribeOn()的上游都被该Scheduler处置,直到遇到另一个unsubscribeOn(),它有自己的上游段。

猜你喜欢

转载自blog.csdn.net/weixin_43364172/article/details/84285509
今日推荐