Reputation: 2991
I have been trying to reproduce (and solve) a ConcurrentModificationException
when an instance of HashMap
is being read and written by multiple Thread
s.
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
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
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
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