1

I'm trying to understand how parallelStreams works. Lets consider the following example:

public class Person {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }  
    }

And a main method:

public static void main(String[] args) {
    final Person person = new Person();
    person.setName("John Doe");
    List<String> myList = Arrays.asList("1","2","3","4","5","6");
    myList.forEach((String element) -> {
        person.setName(person.getName()+element);
        System.out.println(person.getName());
    });
}

The result will be:

John Doe1
John Doe12
John Doe123
John Doe1234
John Doe12345
John Doe123456 

If i change it to parallelStream() the result could be like this:

John Doe4
John Doe41
John Doe415
John Doe4156
John Doe41562
John Doe415623  

Is it possible that less then 6 digits will be appended? For example if two Threads would read the value John Doe at the same time in the beginning. One Thread appends a "1" and the other Thread a "2". When setting the name again one of those would override the other.

Edit:

The reason why i asked: I have a String with some placeholders in it. I wrote my own Spliterator which extracts the placeholders. Furthermore i have a map which provides the replacements for the placeholders. So i apply a filter which checks if each placeholder has a replacement. After that i replace the String. I think replacing the String in the last step will not work correct if it is run in parallel mode.

1
  • 2
    If you run in parallel, you've got data races all over the place. You will need something like an AtomicReference with compareAndSet to make this reliable. Commented Jan 26, 2016 at 18:08

1 Answer 1

3

Yes this is possible. You just need to add more elements in the Stream to make it happen more often.

Consider this:

public static void main(String[] args) {
    final Person person = new Person();
    person.setName("John Doe");
    List<String> myList = IntStream.rangeClosed('a', 'z').mapToObj(c -> String.valueOf((char) c)).collect(Collectors.toList());
    myList.parallelStream().forEach((String element) -> {
        person.setName(person.getName()+element);
        System.out.println(person.getName());
    });
}

which the same as your code but this time we append all the lowercase letters in the alphabet. I just ran it and the last results where:

John Doengopqrswxyztuvabchijkl
John Doengopqrswxyztuvabchijklm
John Doengopqrswxyztuvabcd
John Doengopqrswxyztuvabcde
John Doengopqrswxyztuvabcdef

which means the effect you are describing is definitely happening.

The reason we need to add more elements is just to increase the probability that this effect will happen.


Important note: you haven't said in your question why you wanted this. But remember that mutating external variables is generally something you want to avoid in the first place.

Sign up to request clarification or add additional context in comments.

7 Comments

The last printouts are not necessarily relevant as we have no specified order. Check the name length after the completion of the operation. On my system, the last lines of the printout look similar, still the resulting name has 26 characters appended, in most runs. The reason is that the System.out.println(…) statement within the consumer reduces the likelihood of memory visibility issues. Removing the print statement from the consumer and only checking the result afterwards exhibits a higher likelihood of lost updates.
…and there is no need to collect into a list before the test. IntStream.rangeClosed('a', 'z').parallel().forEach(element -> person.setName(person.getName()+(char)element));
@Holger This is true, I was more pointing out that a shorter name was printed after a longer name, which shouldn't happen if everything was fine. Oh and I agree about the collect, it was more to keep the structure of the OP's code ;).
@kukudas I think it would be better to ask another question about this and add the relevant code.
@kukudas: if the task is like the code of this question, the answer would be, don’t update at all. Just let the stream compute the final result string and call setName a single time afterwards. There is no point in overwriting a property again and again. And you should be aware that it is a lucky coincidence here, that you are using immutable strings. Otherwise, you could also get plenty of other race conditions with such an unprotected concurrent update.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.