我想在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
会容易受到影响吗?
答案 0 :(得分:6)
问题在于链的实现之间的共享状态。这是my blog中的陷阱#8:
假设您对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运算符来实现此目的。