简体   繁体   中英

parallelStream() java 1.8 vs 11

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?

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.

There is no difference in Java 8 and Java 11, for each run we are getting different results. If we want to print properly we can use synchronize block, but we will lose the benefit of parallelStream in this case.

JAVA 8

在此处输入图像描述

在此处输入图像描述

JAVA 11

在此处输入图像描述

在此处输入图像描述

Note that this behaviour is explicitly non deterministic as per the javadoc, so both outputs are valid execution orders.

The behavior of this operation is explicitly nondeterministic. For parallel stream pipelines, this operation does not guarantee to respect the encounter order of the stream, as doing so would sacrifice the benefit of parallelism. For any given element, the action may be performed at whatever time and in whatever thread the library chooses. If the action accesses shared state, it is responsible for providing the required synchronization.

I suppose that if you didn't use the forEachOrdered method and instead you're using forEach on the stream it means that each time you should receive different values no matter which JDK you will use.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM