parallelStream() java 1.8 vs 11

Tags: , , ,



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?

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.



Source: stackoverflow