I want to understand the ordering constraints between nested streams in Java.
Example 1:
public static void main(String[] args) {
IntStream.range(0, 10).forEach(i -> {
System.out.println(i);
IntStream.range(0, 10).forEach(j -> {
System.out.println(" " + i + " " + j);
});
});
}
This code executes deterministically, so the inner loop runs forEach on each j before the outer loop runs its own forEach on the next i:
0
0 0
0 1
0 2
0 3
0 4
0 5
0 6
0 7
0 8
0 9
1
1 0
1 1
1 2
1 3
1 4
1 5
1 6
1 7
1 8
1 9
2
2 0
2 1
2 2
2 3
...
Example 2:
public static void main(String[] args) {
IntStream.range(0, 10).parallel().forEach(i -> {
System.out.println(i);
IntStream.range(0, 10).parallel().forEach(j -> {
System.out.println(" " + i + " " + j);
});
});
}
If the streams are made parallel() as in this second example, I could imagine the inner workers blocking as they wait for threads to become available in the outer work queue, since the outer work queue threads have to block on the completion of the inner stream, and the default thread pool only has a limited number of threads. However, deadlock does not appear to occur:
6
5
8
8 6
0
1
6 2
7
1 6
8 5
7 6
8 8
2
0 6
0 2
0 8
5 2
5 4
5 6
0 5
2 6
7 2
7 5
7 8
6 4
8 9
1 5
...
Both streams share the same default thread pool, yet they generate different work units. Each outer work unit can only complete after all inner units for that outer work unit have completed, since there is a completion barrier at the end of each parallel stream.
How is the coordination between these inner and outer streams managed across the shared pool of worker threads, without any sort of deadlock?
The thread pool behind parallel streams is the common pool, which you can get with ForkJoinPool.commonPool(). It usually uses NumberOfProcessors - 1 workers. To resolve dependencies like you've described, it's able to dynamically create additional workers if (some) current workers are blocked and a deadlock becomes possible.
However, this is not the answer for your case.
Tasks in a ForkJoinPool have two important functionalities:
They can create subtasks and split the current task into smaller pieces (fork).
They can wait for the subtasks (join).
When a thread executes such a task A and joins a subtask B, it doesn't just wait blocking for the subtask to finish its execution but executes another task C in the meantime. When C is finished, the thread comes back to A and checks if B is finished. Note that B and C can (and most likely are) the same task. If B is finished, then A has successfully waited for/joined it (non-blocking!). Check out this guide if the previous explanation is not clear.
Now when you use a parallel stream, the range of the stream is split into tasks recursively until the tasks become so small that they can be executed sequentially more efficiently. Those tasks are put into a work queue (there is one for each worker) in the common pool. So, what IntStream.range(0, 100).parallel().forEach does is splitting up the range recursively until it's not worth it anymore. Each final task, or rather bunch of iterations, can be executed sequentially with the provided code in forEach. At this point the workers in the common pool can just execute those tasks until all are done and the stream can return. Note that the calling thread helps out with the execution by joining subtasks!
Now each of those tasks uses a parallel stream itself in your case. The procedure is the same; split it up into smaller tasks and put those tasks into a work queue in the common pool. From the ForkJoinPool's perspective those are just additional tasks on top of the already present ones. The workers just keep executing/joining tasks until all are done and the outer stream can return.
This is what you see in the output: There is no deterministic behaviour, no fixed order. Also there cannot occur a deadlock because in the given use case there won't be blocking threads.
You can check the explanation with the following code:
public static void main(String[] args) {
IntStream.range(0, 10).parallel().forEach(i -> {
IntStream.range(0, 10).parallel().forEach(j -> {
for (int x = 0; x < 1e6; x++) { Math.sqrt(Math.log(x)); }
System.out.printf("%d %d %s\n", i, j, Thread.currentThread().getName());
for (int x = 0; x < 1e6; x++) { Math.sqrt(Math.log(x)); }
});
});
}
You should notice that the main thread is involved in the execution of the inner iterations, so it is not (!) blocked. The common pool workers just pick tasks one after another until all are finished.
Related
I was investigating Mockito's behavior in one of our tests and I didn't understand its behavior. The following code snippets shows the same behavior :
#Test
public void test(){
var mymock = Mockito.mock(Object.class);
var l = List.of(1,2,3);
l.parallelStream().forEach(val -> {
int finalI = val;
doAnswer(invocationOnMock -> {
log.info("{}",finalI);
return null;
}).when(mymock).toString();
mymock.toString();
});
}
The output I was expecting is prints of 1,2,3 in some order (not nessaseraly sorted) :
The output I received :
2
2
2
Why I got this output ?
This is a classic race condition: your parallel stream executes the forEach lambda three times in parallel. In this case, all three threads managed to execute the doAnswer() call, before any of them got to the mymock.toString() line. It also just so happens that the execution where val=2 happened to run last. Happily, Mockito is reasonably thread-safe, so you don't just get exceptions thrown.
One way your scenario might occur is if the threads happened to run like this:
Main thread: calls l.parallelStream().forEach(), which spawns 3 threads with val equal to 1, 2 and 3
val=1 thread: executes finalI=val, giving it a value of 1 in this thread
val=2 thread: executes finalI=val, giving it a value of 2 in this thread
val=3 thread: executes finalI=val, giving it a value of 3 in this thread
val=3 thread: calls doAnswer(...) which makes future calls to mymock.toString() print out 3
val=1 thread: calls doAnswer(...) which makes future calls to mymock.toString() print out 1
val=2 thread: calls doAnswer(...) which makes future calls to mymock.toString() print out 2
val=1 thread: calls mymock.toString(), which prints out 2
val=2 thread: calls mymock.toString(), which prints out 2
val=3 thread: calls mymock.toString(), which prints out 2
Note that this isn't necessarily happening in order, or even in this order:
some parts of this may literally be happening at the same time, in different cores of your processor.
while any steps within a single thread would always run in order, they might run before, after, or interleaved with any steps from any other thread.
Because the threads are running in parallel, and you have not put anything in place to manage this, there is no guarantee whatsoever about the order in which these calls occur: your output could just as easily have been 1 1 1, 1 2 3, 3 2 1 or 3 2 2 or 1 1 3 etc.
I have defined the following instance variable:
private final AtomicInteger tradeCounter = new AtomicInteger(0);
I have a method called onTrade defined as below being called by 6 threads:
public void onTrade(Trade trade) {
System.out.println(tradeCounter.incrementAndGet());
}
Why is the output:
2
5
4
3
1
6
Instead of
1
2
3
4
5
6
?
I want to avoid using synchronization.
You can think of
tradeCounter.incrementAndGet();
And
System.out.println();
as two separate statements.
So here
System.out.println(tradeCounter.incrementAndGet());
there are basically two statements, and those statements together are not atomical.
Imagine such example with 2 threads :
Thread 1 invokes tradeCounter.incrementAndGet()
Thread 2 invokes tradeCounter.incrementAndGet()
Thread 2 prints value 2
Thread 1 prints value 1
It all depends in what order threads will invoke inctructions in your method.
I have a method called onTrade defined as below being called by 6
threads:
public void onTrade(Trade trade) {
System.out.println(tradeCounter.incrementAndGet());
}
Why is the output:
2 5 4 3 1 6
Instead of 1 2 3 4 5 6 ?
Why shouldn't that be the output? Or why not 3 1 4 6 5 2? Or any of the other permutations of 1 2 3 4 5 6?
Using an AtomicInteger and its incrementAndGet() method ensures that each each thread gets a different value, and that the six values obtained are sequential, without synchronization. But that has nothing to do with the order in which the resulting values are printed afterward.
If you want the results to be printed in the same order that they are obtained, then synchronization is the easiest way to go. In that case, using an AtomicInteger does not gain you anything over using a plain int (for this particular purpose):
int tradeCounter = 0;
synchronized public void onTrade(Trade trade) {
System.out.println(++tradeCounter);
}
Alternatively, don't worry about the order in which the output is printed. Consider: is the output order actually meaningful for any reason?
incrementAndGet() increments in the expected order : 1, 2, 3 etc...
But system.out doesn't invoke the println() in an atomic way along incrementAndGet(). So the random ordering is expected.
I want to avoid using synchronization.
You could not in this case.
System.out.println(...) and tradeCounter.incrementAndGet() are two separate operations and most likely when thread-i gets new value, some other threads can get value and print it before thread-i prints it. There is no way to avoid synchronization (direct or indirect) here.
I am trying to learn concurrency in Java, but whatever I do, 2 threads run in serial, not parallel, so I am not able to replicate common concurrency issues explained in tutorials (like thread interference and memory consistency errors). Sample code:
public class Synchronization {
static int v;
public static void main(String[] args) {
Runnable r0 = () -> {
for (int i = 0; i < 10; i++) {
Synchronization.v++;
System.out.println(v);
}
};
Runnable r1 = () -> {
for (int i = 0; i < 10; i++) {
Synchronization.v--;
System.out.println(v);
}
};
Thread t0 = new Thread(r0);
Thread t1 = new Thread(r1);
t0.start();
t1.start();
}
}
This always give me a result starting from 1 and ending with 0 (whatever the loop length is). For example, the code above gives me every time:
1
2
3
4
5
6
7
8
9
10
9
8
7
6
5
4
3
2
1
0
Sometimes, the second thread starts first and the results are the same but negative, so it is still running in serial.
Tried in both Intellij and Eclipse with identical results. CPU has 2 cores if it matters.
UPDATE: it finally became reproducible with huge loops (starting from 1_000_000), though still not every time and just with small amount of final discrepancy. Also seems like making operations in loops "heavier", like printing thread name makes it more reproducible as well. Manually adding sleep to thread also works, but it makes experiment less cleaner, so to say. The reason doesn't seems to be that first loop finishes before the second starts, because I see both loops printing to console while continuing operating and still giving me 0 at the end. The reasons seems more like a thread race for same variable. I will dig deeper into that, thanks.
Seems like first started thread just never give a chance to second in Thread Race to take a variable/second one just never have a time to even start (couldn't say for sure), so the second almost* always will be waiting until first loop will be finished.
Some heavy operation will mix the result:
TimeUnit.MILLISECONDS.sleep(100);
*it is not always true, but you are was lucky in your tests
Starting a thread is heavyweight operation, meaning that it will take some time to perform. Due that fact, by the time you start second thread, first is finished.
The reasoning why sometimes it is in "revert order" is due how thread scheduler works. By the specs there are not guarantees about thread execution order - having that in mind, we know that it is possible for second thread to run first (and finish)
Increase iteration count to something meaningful like 10000 and see what will happen then.
This is called lucky timing as per Brian Goetz (Author of Java Concurrency In Practice). Since there is no synchronization to the static variable v it is clear that this class is not thread-safe.
If I run the following code:
System.out.println("Common Pool Parallelism: "
+ForkJoinPool.getCommonPoolParallelism());
long start = System.currentTimeMillis();
IntStream.range(0, ForkJoinPool.getCommonPoolParallelism() * 2)
.parallel()
.forEach(i -> {
System.out.println(i +" " +(System.currentTimeMillis() - start) +"ms");
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
});
I get (for example) the following output:
Common Pool Parallelism: 3
5 82ms
3 82ms
1 82ms
0 82ms
4 1087ms
2 1087ms
It appears to me as though the common ForkJoinPool is using 4 threads, then they're blocked for a second, then the last two jobs are run.
But as I understand it, the common ForkJoinPool defaults to using Runtime.getRuntime().availableProcessors() - 1 threads, which in my case is 3 so I'd expect to get three jobs printing at ~82ms not 4.
Is it my code or or understanding that's amiss?
ETA: Printing out Thread.currentThread().getId() within the forEach also shows 4 different thread IDs.
class Runner extends Thread{
public void run(){
for(int i=0;i<5;i++)
{
System.out.println("Counting "+i);
}
}
}
public class App {
public static void main(String args[])
{
Runner runner1=new Runner();
runner1.start();
Runner runner2=new Runner();
runner2.start();
}
}
I was just going through the basics of threads in java. The expected output is :
Counting 0
Counting 0
Counting 1
Counting 1
Counting 2
Counting 2
Counting 3
Counting 3
Counting 4
Counting 4
But am getting an output like this cant seem to reason this out:
Counting 0
Counting 1
Counting 2
Counting 3
Counting 4
Counting 0
Counting 1
Counting 2
Counting 3
Counting 4
Without any kind of synchronization, there can be no expectation to the order of execution (and interleaving of instructions) for multiple threads.
In your example, you could get your expected output by putting long pauses (Thread.sleep) into each iteration of the loop (still technically not guaranteed, but practically speaking unavoidable).
Please provide Thread sleep in your code. It will run concurrently between both the threads.
System.out.println("counting"+i)
Thread.sleep(500)
Please Insert these code in the run method.IT will work as you want