I am trying to understand the SynchronizedMap and I ran the below code. I get the below Output with an exception. According to my understanding the exception is caused when the get() methods are trying to access the syncmap when a thread is still executing a write on the map or Thread is in the Sleep state. Is my understanding correct or am I missing something ?
class MapHelper1 implements Runnable {
Map<String, Integer> map;
public MapHelper1(Map<String, Integer> map) {
this.map = map;
new Thread(this, "MapHelper1").start();
}
public void run() {
map.put("One", 1);
try {
System.out.println("MapHelper1 sleeping");
Thread.sleep(100);
} catch (Exception e) {
System.out.println(e);
}
}
}
class MapHelper2 implements Runnable {
Map<String, Integer> map;
public MapHelper2(Map<String, Integer> map) {
this.map = map;
new Thread(this, "MapHelper3").start();
}
public void run() {
map.put("two", 1);
try {
System.out.println("MapHelper2 sleeping");
Thread.sleep(100);
} catch (Exception e) {
System.out.println(e);
}
}
}
class MapHelper3 implements Runnable {
Map<String, Integer> map;
public MapHelper3(Map<String, Integer> map) {
this.map = map;
new Thread(this, "MapHelper3").start();
}
public void run() {
map.put("three", 1);
try {
System.out.println("MapHelper3 sleeping");
Thread.sleep(100);
} catch (Exception e) {
System.out.println(e);
}
}
}
public class MainClass {
public static void main(String[] args) {
Map<String, Integer> hashMap = new HashMap<>();
Map<String, Integer> syncMap = Collections.synchronizedMap(hashMap);
MapHelper1 mapHelper1 = new MapHelper1(syncMap);
MapHelper2 mapHelper2 = new MapHelper2(syncMap);
MapHelper3 mapHelper3 = new MapHelper3(syncMap);
for (Map.Entry<String, Integer> e : syncMap.entrySet()) {
System.out.println(e.getKey() + "=" + e.getValue());
}
}
}
OUTPUT:
MapHelper1 sleeping
MapHelper2 sleeping
MapHelper3 sleeping
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1494)
at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1527)
at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1525)
at MainClass.main(MainClass.java:137)
Command exited with non-zero status 1
EDIT : I ran the code again a few times when the output was generated without exception. Why is this behavior?
2 Answers 2
You dont synchronize access when iterating. Use:
synchronized(syncMap) {
for (Map.Entry<String, Integer> e : syncMap.entrySet()) {
System.out.println(e.getKey() + "=" + e.getValue());
}
}
It's even in the synchronizedMap() method javadoc
It is imperative that the user manually synchronize on the returned map when iterating over any of its collection views:
Map m = Collections.synchronizedMap(new HashMap());
...
Set s = m.keySet(); // Needn't be in synchronized block
...
synchronized (m) { // Synchronizing on m, not s!
Iterator i = s.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
3 Comments
I just read @Dariusz answer, I think it's totally correct.
To answer specifically why you saw random behavior, it's completely timing. That is, if the map had been populated by all three threads before iteration in Main thread, then everything is fine. But if one thread try to populate while iteration is already underway in Main thread, then you get the exception.
Btw, I know this is for demo purpose. But in real code, better not to start the thread in constructor.
7 Comments
Explore related questions
See similar questions with these tags.
Collections.synchronizedMapproblem. It's valid. And to answer: you have to manually synchronize onsyncMapoutside iteration. Look at the synchronizedMap method javadoc