并行化时RxJava reduce()是否不安全?

时间:2015-06-04 02:09:40

标签: java multithreading reactive-programming rx-java

我想在observable上使用reduce()操作将其映射到番石榴ImmutableList,因为我更喜欢标准ArrayList

Observable<String> strings = ...

Observable<ImmutableList<String>> captured = strings.reduce(ImmutableList.<String>builder(), (b,s) -> b.add(s))
                .map(ImmutableList.Builder::build);

captured.forEach(i -> System.out.println(i));

足够简单。但是假设我在某处安排了可观察的strings与多个线程或其他东西并行。这不会破坏reduce()操作并可能导致竞争条件吗?特别是因为ImmutableList.Builder会容易受到影响吗?

2 个答案:

答案 0 :(得分:6)

问题在于链的实现之间的共享状态。这是my blog中的陷阱#8:

Observable链中的共享状态

假设您对toList()运算符返回的List的性能或类型不满意,并且您想要滚动自己的聚合器而不是它。要进行更改,您希望通过使用现有运算符来执行此操作,并找到运算符reduce():

Observable<Vector<Integer>> list = Observable
    .range(1, 3)
    .reduce(new Vector<Integer>(), (vector, value) -> {
        vector.add(value);
        return vector;
    });

list.subscribe(System.out::println);
list.subscribe(System.out::println);
list.subscribe(System.out::println);

当你运行'测试'调用时,第一个打印你想要的,但第二个打印一个向量,其中范围1-3出现两次,第三个订阅打印9个元素!

问题不在于reduce()运算符本身,而在于它周围的期望。建立链后,传入的新Vector是一个“全局”实例,将在链的所有评估之间共享。

当然,有一种方法可以解决这个问题而不需要为整个目的实现一个操作符(如果你看到前一个CounterOp中的潜力,这应该很简单):

Observable<Vector<Integer>> list2 = Observable
    .range(1, 3)
    .reduce((Vector<Integer>)null, (vector, value) -> {
        if (vector == null) {
            vector = new Vector<>();
        }
        vector.add(value);
        return vector;
    });

list2.subscribe(System.out::println);
list2.subscribe(System.out::println);
list2.subscribe(System.out::println);

你需要从null开始并在累加器函数内创建一个向量,现在不在订阅者之间共享。

或者,您可以查看具有初始值的工厂回调的collect()运算符。

这里的经验法则是,每当您看到类似聚合器的运算符采用某些简单值时,请谨慎,因为这个“初始值”很可能会在所有订阅者之间共享,并且如果您计划使用多个生成的流来使用订阅者,他们会发生冲突,可能会给你意想不到的结果甚至崩溃。

答案 1 :(得分:1)

根据Observable contract,观察者不得并行进行onNext次调用,因此您必须修改strings Observable以尊重此问题。您可以使用serialize运算符来实现此目的。