Stop all threads of a parallelStream [duplicate] - java

I'm trying locate the first (any) member of a list that matches a given predicate like so:
Item item = items.parallelStream()
.map(i -> i.doSomethingExpensive())
.filter(predicate)
.findAny()
.orElse(null);
I would expect that once findAny() gets a match, it would return immediately, but that doesn't appear to be the case. Instead it seems to wait for the map method to finish on most of the elements before returning. How can I return the first result immediately and cancel the other parallel streams? Is there a better way to do this than using streams such as CompletableFuture?
Here's a simple example to show the behavior:
private static void log(String msg) {
private static void log(String msg) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
System.out.println(sdf.format(new Date()) + " " + msg);
}
Random random = new Random();
List<Integer> nums = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14);
Optional<Integer> num = nums.parallelStream()
.map(n -> {
long delay = Math.abs(random.nextLong()) % 10000;
log("Waiting on " + n + " for " + delay + " ms");
try { Thread.sleep(delay); }
catch (InterruptedException e) { System.err.println("Interruption error"); }
return n * n;
})
.filter(n -> n < 30)
.peek(n -> log("Found match: " + n))
.findAny();
log("First match: " + num);
Log output:
14:52:27.061 Waiting on 9 for 2271 ms
14:52:27.061 Waiting on 2 for 1124 ms
14:52:27.061 Waiting on 13 for 547 ms
14:52:27.061 Waiting on 4 for 517 ms
14:52:27.061 Waiting on 1 for 1210 ms
14:52:27.061 Waiting on 6 for 2646 ms
14:52:27.061 Waiting on 0 for 4393 ms
14:52:27.061 Waiting on 12 for 5520 ms
14:52:27.581 Found match: 16
14:52:27.582 Waiting on 3 for 5365 ms
14:52:28.188 Found match: 4
14:52:28.275 Found match: 1
14:52:31.457 Found match: 0
14:52:32.950 Found match: 9
14:52:32.951 First match: Optional[0]
Once a match is found (in this case 16), findAny() does not return immediately, but instead blocks until the remaining threads finish. In this case, the caller is waiting an extra 5 seconds before returning after a match has already been found.

Instead it seems to wait for the map method to finish on most of the elements before returning.
This is not correct.
When speaking of the elements which are already being processed, it will wait for the completion of all of them, as the Stream API allows concurrent processing of data structures which are not intrinsically thread safe. It must ensure that all potential concurrent access has been finished before returning from the terminal operation.
When talking about the entire stream, it’s simply not fair to test a stream of only 14 elements on an 8 core machine. Of course, there will be at least 8 concurrent operations started, that’s what it is all about. You are adding fuel to the flames by using findFirst() instead of findAny(), as that doesn’t mean returning the first found element in processing order, but the first element in encounter order, i.e. exactly zero in your example, so threads processing other chunks than the first can’t assume that their result is the correct answer and are even more willing to help processing other candidates than with findAny().
When you use
List<Integer> nums = IntStream.range(0, 200).boxed().collect(Collectors.toList());
Optional<Integer> num = nums.parallelStream()
.map(n -> {
long delay = ThreadLocalRandom.current().nextInt(10_000);
log("Waiting on " + n + " for " + delay + " ms");
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(delay));
return n * n;
})
.filter(n -> n < 40_000)
.peek(n -> log("Found match: " + n))
.findAny();
log("First match: " + num);
You will get a similar number of tasks running to completion, despite the far bigger number of stream elements.
Note that CompletableFuture also doesn’t support interruption, so the only builtin feature for returning any result and canceling the other jobs, that comes into my mind, is the old ExecutorService.invokeAny.
To built the feature of mapping and filtering for it, we can use the following helper function:
static <T,R> Callable<R> mapAndfilter(T t, Function<T,R> f, Predicate<? super R> p) {
return () -> {
R r = f.apply(t);
if(!p.test(r)) throw new NoSuchElementException();
return r;
};
}
Unfortunately, there’s only the option of completing with a value or exceptionally, therefore we have to use an exception for non-matching elements.
Then we can use it like
ExecutorService es = ForkJoinPool.commonPool();
Integer result = es.invokeAny(IntStream.range(0, 100)
.mapToObj(i -> mapAndfilter(i,
n -> {
long delay = ThreadLocalRandom.current().nextInt(10_000);
log("Waiting on " + n + " for " + delay + " ms");
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(delay));
return n * n;
},
n -> n < 10_000))
.collect(Collectors.toList()));
log("result: "+result);
and it will not only cancel the pending tasks, it will return without waiting for them to finish.
Of course, this implies that the source data, the jobs operating upon, must be either immutable or thread safe.

You can use this code to illustrate how parallelStream works:
final List<String> list = Arrays.asList("first", "second", "third", "4th", "5th", "7th", "8th", "9th", "10th", "11th", "12th", "13th");
String result = list.parallelStream()
.map(s -> {
System.out.println("map: " + s);
return s;
})
.filter(s -> {
System.out.println("fiter: " + s);
return s.equals("8th");
})
.findFirst()
.orElse(null);
System.out.println("result=" + result);
There are two options to achieve what you're looking for, to stop expensive operation with a filter:
Don't use streams at all, use a simple for or enhanced for
Filter first, then map with the expensive operation

There are several things at play here. The first thing is that parallelStream() uses the common ForkJoinPool by default, which makes the calling thread participate as well. This means that if one of the slow tasks is currently running on the calling thread, it has to finish before the caller gets the control back.
You can see this by modifying the code a little bit to log the thread names, and log when finished the wating:
private static void log(String msg) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
System.out.println(sdf.format(new Date()) + " [" + Thread.currentThread().getName() + "] " + " " + msg);
}
public static void main(String[] args) {
Random random = new Random();
List<Integer> nums = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14);
Optional<Integer> num = nums.parallelStream()
.map(n -> {
long delay = Math.abs(random.nextLong()) % 10000;
log("Waiting on " + n + " for " + delay + " ms");
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
System.err.println("Interruption error");
}
log("finished waiting");
return n * n;
})
.filter(n -> n < 30)
.peek(n -> log("Found match: " + n))
.findAny();
log("First match: " + num);
}
Sample output:
13:56:52.954 [main] Waiting on 9 for 9936 ms
13:56:52.956 [ForkJoinPool.commonPool-worker-1] Waiting on 4 for 7436 ms
13:56:52.970 [ForkJoinPool.commonPool-worker-2] Waiting on 1 for 6523 ms
13:56:52.983 [ForkJoinPool.commonPool-worker-3] Waiting on 6 for 7488 ms
13:56:59.494 [ForkJoinPool.commonPool-worker-2] finished waiting
13:56:59.496 [ForkJoinPool.commonPool-worker-2] Found match: 1
13:57:00.392 [ForkJoinPool.commonPool-worker-1] finished waiting
13:57:00.392 [ForkJoinPool.commonPool-worker-1] Found match: 16
13:57:00.471 [ForkJoinPool.commonPool-worker-3] finished waiting
13:57:02.892 [main] finished waiting
13:57:02.894 [main] First match: Optional[1]
Here as you can see, 2 matches are found but the main thread is still busy, so it cannot return the match now.
This does not always explain all cases though:
13:58:52.116 [main] Waiting on 9 for 5256 ms
13:58:52.143 [ForkJoinPool.commonPool-worker-1] Waiting on 4 for 4220 ms
13:58:52.148 [ForkJoinPool.commonPool-worker-2] Waiting on 1 for 2136 ms
13:58:52.158 [ForkJoinPool.commonPool-worker-3] Waiting on 6 for 7262 ms
13:58:54.294 [ForkJoinPool.commonPool-worker-2] finished waiting
13:58:54.295 [ForkJoinPool.commonPool-worker-2] Found match: 1
13:58:56.364 [ForkJoinPool.commonPool-worker-1] finished waiting
13:58:56.364 [ForkJoinPool.commonPool-worker-1] Found match: 16
13:58:57.399 [main] finished waiting
13:58:59.422 [ForkJoinPool.commonPool-worker-3] finished waiting
13:58:59.424 [main] First match: Optional[1]
This might be explained by the way the fork-join pool merges the results. It seems some improvements are possible.
As an alternative, you could indeed do this using CompletableFuture:
// you should probably also pass your own executor to supplyAsync()
List<CompletableFuture<Integer>> futures = nums.stream().map(n -> CompletableFuture.supplyAsync(() -> {
long delay = Math.abs(random.nextLong()) % 10000;
log("Waiting on " + n + " for " + delay + " ms");
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
System.err.println("Interruption error");
}
log("finished waiting");
return n * n;
})).collect(Collectors.toList());
CompletableFuture<Integer> result = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(unused -> futures.stream().map(CompletableFuture::join).filter(n -> n < 30).findAny().orElse(null));
// shortcircuiting
futures.forEach(f -> f.thenAccept(r -> {
if (r < 30) {
log("Found match: " + r);
result.complete(r);
}
}));
// cancelling remaining tasks
result.whenComplete((r, t) -> futures.forEach(f -> f.cancel(true)));
log("First match: " + result.join());
Output:
14:57:39.815 [ForkJoinPool.commonPool-worker-1] Waiting on 0 for 7964 ms
14:57:39.815 [ForkJoinPool.commonPool-worker-3] Waiting on 2 for 5743 ms
14:57:39.817 [ForkJoinPool.commonPool-worker-2] Waiting on 1 for 9179 ms
14:57:45.562 [ForkJoinPool.commonPool-worker-3] finished waiting
14:57:45.563 [ForkJoinPool.commonPool-worker-3] Found match: 4
14:57:45.564 [ForkJoinPool.commonPool-worker-3] Waiting on 3 for 7320 ms
14:57:45.566 [main] First match: 4
Note that the cancel(true) does not actually cancel the ongoing tasks (no interruption will occur for example), but it prevents further tasks from being run (you can even see that it might not be immediate since worker 3 still started to execute the next one).
You should also use your own executor, with the appropriate size based on whether it is more CPU or I/O intensive. As you can see, the default uses the common pool and thus it does not use all cores.
The allOf() is mainly needed in case no match is found. If you can guarantee that there is at least one match, you could simply use a `new CompletableFuture() instead.
Finally, as a simple approach I repeated the filter check, but it's easy to move that logic inside the main logic, return null or a marker, and then test for that in both places.
See also How to make a future that gets completed when any of the given CompletableFutures is completed with a result that matches a certain predicate?

Related

How to use join for a stream of CompletableFuture

Supposedly, I have the following method
public static Stream<CompletableFuture<String>> findPricesStream(String product) {}
This method will look for the cheapest price given a product and it returns a stream of CompletableFuture.
Now I want to react to the value in the stream as soon as it is available. To do that,
I adopt the method thenAccept and the implementation could be as following
1. public static void reactToEarliestResultWithRaw() {
2. long start = System.nanoTime();
3. CompletableFuture[] priceFuture = findPricesStream(WHATEVER_PRODUCT_NAME)
4. .map(completableFuture -> completableFuture.thenAccept(
5. s -> System.out.println(s + " (done in " + (System.nanoTime() - start) / 1_000_000 + " msecs)")))
6. .toArray(CompletableFuture[]::new);
7. CompletableFuture.allOf(priceFuture).join();
8. System.out.println("All shops have now responded in " + (System.nanoTime() - start) / 1_000_000 + " msecs");
9. }
With this implementation, I got the desired output
LetsSaveBig price is 151.227 (done in 5476 msecs)
BuyItAll price is 211.66 (done in 5747 msecs)
MyFavoriteShop price is 206.30200000000002 (done in 6968 msecs)
BestPrice price is 131.917 (done in 8110 msecs)
All shops have now responded in 8110 msecs
Now I would like to take a further step to make the code more readable.
I chained another map that is responsible for joining all of CompletableFuture
1. public static void reactToEarliestResultWithoutRaw() {
2. long start = System.nanoTime();
3. List<Void> completeFutures = findPricesStream(WHATEVER_PRODUCT_NAME)
4. .map(completableFuture -> completableFuture.thenAccept(
5. s -> System.out.println(s + " (done in " + (System.nanoTime() - start) / 1_000_000 + " msecs)")))
6. .map(CompletableFuture::join)
7. .toList();
8. int size = completeFutures.size();
9. if (isComplete(size)) {
10. System.out.println("All shops have now responded in " + (System.nanoTime() - start) / 1_000_000 + " msecs");
11. }
12.
13. private static boolean isComplete(int size) {
14. return size == shops.size();
15. }
I got the output
BestPrice price is 123.17400000000002 (done in 2060 msecs)
LetsSaveBig price is 109.67200000000001 (done in 6025 msecs)
MyFavoriteShop price is 131.21099999999998 (done in 13860 msecs)
BuyItAll price is 164.392 (done in 18434 msecs)
All shops have now responded in 18434 msecs
The result makes me surprised!
I expect the elapsed time for both should be somehow the same, but they are a huge difference.
Do I misunderstand the way of using join here?
Reference
The implementation comes from the book Modern Java in Action: Lambdas, streams, functional and reactive programming 2nd Edition and
I have modified it a bit for the experiment.
The "surprising" results are due to how findPricesStream is implemented: it returns shops.stream().map(shop -> CompletableFuture.supplyAsync(...). The CompletableFuture is not constructed until a terminal operation is applied to the returned stream. This is done in your own method, after you call .toList().
The terminal operation toList() does this:
For the first shop, it constructs a CompletableFuture, which starts running.
The CompletableFuture is joined, i.e. the main thread waits until it is finished.
Then the next CompletableFuture is constructed for the next shop, and so on.
So the prices are calculated sequentially. To make the calculations run in parallel, create the list first (so that all futures are started) and then join them:
3. List<CompletableFuture<Void>> futures = findPricesStream(WHATEVER_PRODUCT_NAME)
4. .map(completableFuture -> completableFuture.thenAccept(
5. s -> System.out.println(s + " (done in " + (System.nanoTime() - start) / 1_000_000 + " msecs)")))
6. .toList();
7. List<Void> results = futures.stream()
.map(CompletableFuture::join)
.toList();

Flux stream after .map() function not in order

while trying to get familiar with Flux I want to have a stream of Data within a Flux.
the .map() function should simulate a workload which requires some random processing time per Magician object and is happening in separate Threads (as you can see in the output at Thread id to make use of Multi-Threading.
Finally the stream should return the results in order. But no matter what I do, e.g. using .sequential(), or where it always ends up in in being unordered.
Can someone tell me how I can make use of Multi-Threading (using .map() or any other sequence as long as the result is the one asked) and having the results in order please? Thank you
P.S. The business logic behind this question is that I am calling an Endpoint that delivers a Flux<byte[]>
These bytes I need to process in parallel (decryption) and therefor forward in order to another consumer.
public class FluxRunner {
public void getWorkerStream() throws InterruptedException {
final int[] counter = new int[1];
// create workers
final Flux<Magician[]> workersFlux = Flux.range(0, 10)
.map(integer -> {
final Magician[] worker = new Magician[1];
worker[0] = new Magician(counter[0], counter[0]++);
return worker;
});
final Disposable disposable = workersFlux
.parallel()
.runOn(Schedulers.parallel())
.map(workers -> {
System.out.println("Thread id: " + Thread.currentThread().getId());
workers[0].calculate();
return workers;
})
.sequential() // no effect, doOnNext is unordered
.doOnNext(workers -> System.out.println(workers[0].getId()))
.subscribe();
while (!disposable.isDisposed()) {
sleep(500);
}
}
}
#Data
class Magician {
final int id;
int number;
Magician(final int id, final int number) {
this.id = id;
this.number = number;
}
public void calculate() {
int timeToSleep = (int) (Math.random() * 3000);
System.out.println("Sleep for " + timeToSleep + " seconds");
try {
sleep(timeToSleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
number++;
}
}
result will be
Thread id: 33
Thread id: 24
Thread id: 25
Thread id: 31
Thread id: 29
Thread id: 30
Thread id: 27
Thread id: 32
Thread id: 28
Thread id: 26
Sleep for 2861 seconds
Sleep for 2811 seconds
Sleep for 711 seconds
Sleep for 2462 seconds
Sleep for 1858 seconds
Sleep for 601 seconds
Sleep for 126 seconds
Sleep for 359 seconds
Sleep for 2014 seconds
Sleep for 2356 seconds
4
5
7
9
8
3
0
1
6
2
You can use flatMapSequential operator along with .subscribeOn(Schedulers.parallel()) to get the desired result:
final Disposable disposable = workersFlux
.flatMapSequential(workers -> Mono.fromCallable(() -> {
System.out.println("Thread id: " + Thread.currentThread().getId());
workers[0].calculate();
return workers;
}).subscribeOn(Schedulers.parallel()))
.doOnNext(workers -> System.out.println(workers[0].getId()))
.subscribe();

Hazelcast Ringbuffer readManyAsync returns Empty Results

I'm trying to read N items from a RingBuffer using readManyAsync but It's always returns an empty resultSet. If I use readOne I get data.
I'm using the readManyAsync as the documentation specify. There is another way to do that?
Enviroment:
Java 8
Hazelcast 3.5.3
Example:
Ringbuffer<String> buffer = this.hazelcastInstance.getRingbuffer("testBuffer");
buffer.add("a");
buffer.add("b");
buffer.add("c");
Long sequence = buffer.headSequence();
ICompletableFuture<ReadResultSet<String>> resultSetFuture = buffer.readManyAsync(sequence, 0, 3, null);
ReadResultSet<String> resultSet = resultSetFuture.get();
System.out.println("*** readManyAsync *** readCount: " + resultSet.readCount());
int count = 0;
for (String s : resultSet) {
System.out.println(count + " - " + s);
count++;
}
System.out.println("*** readOne ***");
for (int i = 0; i < 3; i++) {
System.out.println(i + " - " + buffer.readOne(i));
}
Output:
*** readManyAsync *** readCount: 0
*** readOne ***
0 - a
1 - b
2 - c
You are happy with receiving zero results:
buffer.readManyAsync(sequence, 0, 3, null);
Try changing 0 to 1.
buffer.readManyAsync(sequence, 1, 3, null);
Now the call will block till there is at least 1 result.
Probably you can make things more efficient by asking for more than 3 items. In most cases, retrieving data is cheap, but the io/operation scheduling is expensive. So try to batch as much as possible. So try to get as many results as possible.. e.g. 100... or 1000 (which is the max).
Ok, but how do you use readManyAsync in a non-blocking way, with minCount to 0 ?
I made a minimal test case, and I really can't figure it out. I posted a support topic here :
https://groups.google.com/forum/#!topic/hazelcast/FGnLWDGrzb8
As an answer : I use readManyAsync with a timeout, like so :
try{
buffer.readManyAsync(sequence, 1, 3, null).get(500, TimeUnit.MILLISECONDS);
} catch (TimeoutException e){
// We timed out, shame, let's move on
}
That seems the only way to make a graceful non-blocking thread, but by reading the doc, I really thought a minCount=0 would do the trick.

Why is my RxJava observable not firing off subscribers?

I'm messing around with RxJava and I want to stream a thousand consecutive integers. Then I want to asynchronously split them into odd and even streams, and then print them asynchronously.
However, I'm getting nothing printed out or at least very partial output. What am I missing? Did I schedule incorrectly? Or is the console having multithreading issues in Eclipse?
public static void main(String[] args) {
List<Integer> values = IntStream.range(0,1000).mapToObj(i -> Integer.valueOf(i)).collect(Collectors.toList());
Observable<Integer> ints = Observable.from(values).subscribeOn(Schedulers.computation());
Observable<Integer> evens = ints.filter(i -> Math.abs(i) % 2 == 0);
Observable<Integer> odds = ints.filter(i -> Math.abs(i) % 2 != 0);
evens.subscribe(i -> System.out.println(i + " IS EVEN " + Thread.currentThread().getName()));
odds.subscribe(i -> System.out.println(i + " IS ODD " + Thread.currentThread().getName()));
}
You are starting the processing pipeline using Schedules.computation which runs daemon threads. Thus when your main thread finishes, those threads are terminated before processing your observable.
So if you would like to see your results printed you could have your main thread wait for the results (e.g. by Thread.sleep) or subscribe on the calling thread by removing subscribeOn. There is also an option to create your own scheduler which will run non-daemon threads.

Reduction behaves strangely when using parallel stream but works fine for sequential stream in Java 8u5

class Foo{
int len;
}
public class Main {
public static void main(String[] args) throws Exception{
System.out.println(Stream.of("alpha", "beta", "gamma", "delta").parallel().reduce(
new Foo(),
(f, s) -> { f.len += s.length(); return f; },
(f1, f2) -> {
Foo f = new Foo();
/* check self-reduction
if (f1 == f2) {
System.out.println("equal");
f.len = f1.len;
return f;
}
*/
f.len = f1.len + f2.len;
return f;
}
).len);
}
The code tries to count the total length of several strings.
This piece of code prints 19 only if
1.I use sequential stream (by removing the "parallel()" function call)
or
2.I use Integer instead of Foo which is simply a wrapper around an int.
Otherwise the console will print 20 or 36 instead. To debug this issue, I added the code "check self-reduction" which does change the output: "equal" always gets printed twice. The console will sometimes print 8, sometimes 10.
My understanding is that reduce() is a Java implementation of parallel foldr/foldl. The 3rd argument of reduce(), combiner is used to merge results of parallel execution of reduction. Is that right? If so, why would the result of reduction ever need to combine with itself? Further, how do I fix this code so that it gives correct output and still runs parallel?
EDIT:
Please ignore the fact that I did not use method reference to simplify the code, as my ultimate goal was to zip by adding more fields to Foo.
Your code is horribly broken. You are using a reducer function which fails the requirement that the accumulator/combiner functions be associative, stateless, and non-interfering. And a mutable Foo is not an identity for the reduction. All of these can lead to incorrect results when executed in parallel.
You're also making it far harder than you need to! Try this:
int totalLen =
Stream.of(... stuff ...)
.parallel()
.mapToInt(String::length)
.sum();
or
int totalLen =
Stream.of(... stuff ...)
.parallel()
.mapToInt(String::length)
.reduce(0, Integer::sum);
Further, you're trying to use reduce which reduces over values (which is why it works with Integer), but you're trying to use mutable state containers for your reduction result. If you want to reduce into a mutable state container (like a List or StringBuilder), use collect() instead, which is designed for mutation.
I think the problem is that the "identity" Foo is being reused too much.
Here's a modification where each Foo is given its own ID number so that we can track it:
class Foo {
private static int currId = 0;
private static Object lock = new Object();
int id;
int len;
public Foo() {
synchronized(lock) {
id = currId++;
}
}
}
public class Main {
public static void main(String[] args) throws Exception{
System.out.println(Stream.of("alpha", "beta", "gamma", "delta").parallel().reduce(
new Foo(),
(f, s) -> {
System.out.println("Adding to #" + f.id + ": " +
f.len + " + " + s.length() + " => " + (f.len+s.length()));
f.len += s.length(); return f; },
(f1, f2) -> {
Foo f = new Foo();
f.len = f1.len + f2.len;
System.out.println("Creating new #" + f.id + " from #" + f1.id + " and #" + f2.id + ": " +
f1.len + " + " + f2.len + " => " + (f1.len+f2.len));
return f;
}
).len);
}
The output I get is:
Adding to #0: 0 + 5 => 5
Adding to #0: 0 + 4 => 4
Adding to #0: 5 + 5 => 10
Adding to #0: 9 + 5 => 14
Creating new #2 from #0 and #0: 19 + 19 => 38
Creating new #1 from #0 and #0: 14 + 14 => 28
Creating new #3 from #2 and #1: 38 + 28 => 66
66
It's not consistent every time. The thing I notice is that each time you say f.len += s.length(), it adds to the same Foo, which means that the first new Foo() is being executed only once, and lengths keep getting added into it, so that the same input strings' lengths are counted multiple times. Since there are apparently multiple parallel threads accessing it at the same time, the results above are a little strange and change from run to run.

Categories