Consider the following code:
public class StreamDemo { public static void main(String[] args) { StreamObject obj = new StreamObject(); obj.setName("mystream"); List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5)); list.parallelStream().forEach(l -> { obj.setId(l); System.out.println(obj + Thread.currentThread().getName()); }); } static public class StreamObject { private String name; private Integer id; // getters, setters, toString() } }
When it is compiled and run with java 11, it returns the following:
StreamObject{name='mystream', id=4}ForkJoinPool.commonPool-worker-23 StreamObject{name='mystream', id=4}main StreamObject{name='mystream', id=4}ForkJoinPool.commonPool-worker-9 StreamObject{name='mystream', id=4}ForkJoinPool.commonPool-worker-5 StreamObject{name='mystream', id=4}ForkJoinPool.commonPool-worker-19
But with java 1.8, it returns different result:
StreamObject{name='mystream', id=3}main StreamObject{name='mystream', id=5}ForkJoinPool.commonPool-worker-2 StreamObject{name='mystream', id=2}ForkJoinPool.commonPool-worker-9 StreamObject{name='mystream', id=1}ForkJoinPool.commonPool-worker-11 StreamObject{name='mystream', id=4}ForkJoinPool.commonPool-worker-4
Why results are different?
Advertisement
Answer
Both results are consistent with the Java Memory Model.
One possible ordering in which execution occurs is:
T1 calls setId T1 prints T2 calls setId T2 prints ... T5 calls setId T5 prints
but, because you don’t do anything to ensure that the set and print occur atomically, the following is also allowed (as are many other orderings):
T3 calls setId T1 calls setId T2 calls setId T5 calls setId T4 calls setId T1 prints T1 prints ... T5 prints
So, the reason they’re different is because the specification doesn’t require them to be the same; and some subtle (or perhaps not-so-subtle) implementation (or environmental) difference means they execute differently.
But, you say, what is the implementation difference? That’s not something you should need to care about (which sounds like bluster to cover for not knowing: I really don’t). You should care about the Java Memory Model, because that gives the guaranteed properties.
For example, if you want the “Java 8” behaviour, you can synchronize the threads on a common monitor, for example obj
:
list.parallelStream().forEach(l -> { synchronized (obj) { obj.setId(l); System.out.println(obj + Thread.currentThread().getName()); } });
Of course, the threads still will execute in an arbitrary order; but each thread will print the value that it set.