Genzer
Genzer

Reputation: 2991

Does Collection.stream() have internal synchronization?

I have been trying to reproduce (and solve) a ConcurrentModificationException when an instance of HashMap is being read and written by multiple Threads.

Disclaimer: I know that HashMap is not thread-safe.

In the following code:

import java.util.*;

public class MyClass {

    public static void main(String args[]) throws Exception {
        java.util.Map<String, Integer> oops = new java.util.HashMap<>();
        oops.put("1", 1);
        oops.put("2", 2);
        oops.put("3", 3);

        Runnable read = () -> {
            System.out.println("Entered read thread");

            /*
             * ConcurrentModificationException possibly occurs
             *
            for (int i = 0; i < 100; i++) {
                List<Integer> numbers = new ArrayList<>();
                numbers.addAll(oops.values());
                System.out.println("Size " + numbers.size());
            }
            */

            for (int i = 0; i < 100; i++) {
                List<Integer> numbers = new ArrayList<>();
                numbers.addAll(oops.values()
                        .stream()
                        .collect(java.util.stream.Collectors.toList()));
                System.out.println("Size " + numbers.size());
            }
        };

        Runnable write = () -> {
            System.out.println("Entered write thread");
            for (int i = 0; i < 100; i++) {
                System.out.println("Put " + i);
                oops.put(Integer.toString(i), i);
            }
        };

        Thread writeThread = new Thread(write, "write-thread");
        Thread readThread = new Thread(read, "read-thread");

        readThread.start();
        writeThread.start();

        readThread.join();
        writeThread.join();
    }
}

Basically, I make two threads: one keeps putting elements into a HashMap, the other is iterating on HashMap.values().

In the read thread, if I'm using numbers.addAll(oops.values()), the ConcurrentModificationException randomly occurs. Though the lines are printed randomly as expected.

But if I switch to numbers.addAll(oops.values().stream().., I don't get any error. However, I have observed a strange phenomenon. All the lines by the read thread are printed after the lines printed by the write thread.

My question is, does Collection.stream() have somehow internal synchronization?

UPDATE:

Using JDoodle https://www.jdoodle.com/a/IYy, it seems on JDK9 and JDK10, I will get ConcurrentModificationException as expected.

Thanks!

Upvotes: 5

Views: 177

Answers (3)

Eugene
Eugene

Reputation: 120858

What you are seeing is absolutely by chance; bear in mind that internally System.out.println does a synchronzied; thus may be that somehow makes it look like the results appear in order.

I have not looked too deep into your code - because analyzing why HashMap, which is not thread safe, is miss behaving is most probably futile; as you know, it is documented to be non-thread safe.

About that ConcurrentModificationException, the documentation is specific that it will try at best odds to throw that; so it's either java-8 was weaker in this point, or this was again by accident.

Upvotes: 1

grape_mao
grape_mao

Reputation: 1153

I've looked at the source code of Java 8 quickly, it does throw ConcurrentModificationException. HashMap's values()method returns a subclass of AbstractCollection, whose spliterator() method returns a ValueSpliterator, which throws ConcurrentModificationException.

For information Collection.stream() uses a spliterator to traverse or partition elements of a source.

Upvotes: 0

Ivan
Ivan

Reputation: 8758

I was able to get ConcurrentModificationException with streams on Java 8 but with some changes in code: increased number of iterations and number of added elements to map in a separate thread from 100 to 10000. And also added CyclicBarrier so that loops in reader and writer threads are started more or less at the same time. I've also checked source code of spliterator for Hashmap.values() and it throws ConcurrentModificationException if some modifications to map were made.

if (m.modCount != mc) //modCount is number of modifications mc is expected modifications count which is stored before trying to fetch next element
                throw new ConcurrentModificationException();

Upvotes: 1

Related Questions