我想创建一个java.util.stream.Stream
,该块在terminal actions上阻塞,并使用任意对象进行同步。 Stream的方法本身必须以透明的方式进行操作,以便我可以安全地将Stream传递给不知道同步的代码。
考虑以下示例:
void libraryMethod(Stream<Whatever> s) {
for (int i = 0; i < 10000000; ++i) { /* ... */ }
s.filter(Library::foo).forEach(Library::bar);
}
/* Elsewhere in my code */
Set<Whatever> aSet = Collections.synchronizedSet(...);
/* ... */
libraryMethod(new MyBlockingStream<>(set.stream(), set));
在执行forEach
之前,我希望aSet
由MyBlockingStream本身获取 并仅在forEach
终止时释放。这应该保证我不会得到ConcurrentModificationException
,因为其他线程可能要修改该集合。我无法在整个synchronized (aSet)
上使用libraryMethod
,因为那样会阻塞aSet
所需的时间更长。
是否可以这样做?如果是这样,是否有任何现有的实现方式可以执行此操作,或者我必须自己编写它?
注意:这个问题与Stream如何执行动作无关-我不在乎它是否并行。我知道存在本质上不可同步的iterator()
和spliterator()
方法。我也不在乎他们。
答案 0 :(得分:0)
您可以使用锁。
public class Example {
public static ReentrantLock lock = new ReentrantLock();
private static void sleep() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static Runnable createRunnable() {
return () -> {
try {
Arrays.asList("a", "b", "c").stream().forEach(e -> {
if (!lock.isHeldByCurrentThread())
lock.lock();
sleep();
System.out.println(String.format("thread %s with element %s", Thread.currentThread().getId(), e));
});
} finally {
if(lock.isHeldByCurrentThread())
lock.unlock();
}
};
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(createRunnable());
Thread t2 = new Thread(createRunnable());
Thread t3 = new Thread(createRunnable());
t1.start();
t2.start();
t3.start();
System.out.println("join all threads");
t1.join();
t2.join();
t3.join();
}
}
首先到达forEach
的线程将锁定所有其他线程。
在这种情况下的输出是
join all threads
thread 16 with element a
thread 16 with element b
thread 16 with element c
thread 15 with element a
thread 15 with element b
thread 15 with element c
thread 14 with element a
thread 14 with element b
thread 14 with element c
正如@Holger指出的,如果流被嵌套,这将不起作用。内部流将太早释放锁定。
答案 1 :(得分:0)
这就是我最终要做的:https://gist.github.com/OLEGSHA/bda28ffaa4b24e64b94a8c30c3ad9b0c。这些流包装器synchronize
使用提供的对象public void forEach(Consumer<? super T> action) {
synchronized (monitor) {
parent.forEach(action);
}
}
进行所有终端操作,并包装来自中间操作的流。
这本质上是
Stream
用于IntStream
界面中的每个终端操作。包括LongStream
,DoubleStream
和{{1}}版本。
我认为必须有更好的解决方案。