9

I'm currently working on a multi-threaded application, and I occasionally receive a concurrently modification exception (approximately once or twice an hour on average, but occurring at seemingly random intervals).

The faulty class is essentially a wrapper for a map -- which extends LinkedHashMap (with accessOrder set to true). The class has a few methods:

synchronized set(SomeKey key, SomeValue val)

The set method adds a key/value pair to the internal map, and is protected by the synchronized keyword.

synchronized get(SomeKey key)

The get method returns the value based on the input key.

rebuild()

The internal map is rebuilt once in a while (~every 2 minutes, intervals do not match up with the exceptions). The rebuild method essentially rebuilds the values based on their keys. Since rebuild() is fairly expensive, I did not put a synchronized keyword on the method. Instead, I am doing:

public void rebuild(){
  /* initialization stuff */
  List<SomeKey> keysCopy = new ArrayList<SomeKey>();
  synchronized (this) {
    keysCopy.addAll(internalMap.keySet());
  }
  /* 
    do stuff with keysCopy, update a temporary map
   */    
  synchronized (this) {
    internalMap.putAll(tempMap);
  }
}

The exception occurs at

keysCopy.addAll(internalMap.keySet());

Inside the synchronized block.

Suggestions are greatly appreciated. Feel free to point me to specific pages/chapters in Effective Java and/or Concurrency in Practice.

Update 1:

Sanitized stacktrace:

java.util.ConcurrentModificationException
        at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:365)
        at java.util.LinkedHashMap$KeyIterator.next(LinkedHashMap.java:376)
        at java.util.AbstractCollection.toArray(AbstractCollection.java:126)
        at java.util.ArrayList.addAll(ArrayList.java:473)
        at a.b.c.etc.SomeWrapper.rebuild(SomeWraper.java:109)
        at a.b.c.etc.SomeCaller.updateCache(SomeCaller.java:421)
        ...

Update 2:

Thanks everyone for the answers so far. I think the problem lies within the LinkedHashMap and its accessOrder attribute, although I am not entirely certain atm (investigating).

If accessOrder on a LinkedHashMap is set to true, and I access its keySet then proceed to add the keySet to a linkedList via addAll, do either of these actions mutate the order (i.e. count towards an "access")?

Cambium
  • 19,152
  • 3
  • 26
  • 19
  • OT, but arn't you in danger of missing any changes to the class in the commented region between the two synchronized methods in rebuild()? – DaveR Jul 06 '09 at 22:51
  • Yes, you are absolutely correct; but according to the design docs, it's acceptable to have somewhat stale data :) – Cambium Jul 06 '09 at 22:53
  • 2
    Damn, I wish I had design docs like that ;) – DaveR Jul 06 '09 at 22:54
  • Sorry, but can you show the declaration and initialization of the internalMap? – Tom Jul 06 '09 at 23:14
  • could you also show that ConcurrentModificationException stack – akf Jul 06 '09 at 23:20
  • @Tom: Sorry, I can't really disclose the implementation of the 'InternalMap' class. Hope you can understand :) – Cambium Jul 06 '09 at 23:30
  • @ akf: I sanitized the stacktrace a little, hopefully this is sufficient: java.util.ConcurrentModificationException at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:365) at java.util.LinkedHashMap$KeyIterator.next(LinkedHashMap.java:376) at java.util.AbstractCollection.toArray(AbstractCollection.java:126) at java.util.ArrayList.addAll(ArrayList.java:473) at a.b.c.etc.SomeWrapper.rebuild(SomeWraper.java:109) at a.b.c.etc.SomeCaller.updateCache(SomeCaller.java:421) ... – Cambium Jul 06 '09 at 23:30
  • Are you modifying the set in another thread, e.g. adding/removing elements in response to a message received over the network? – finnw Jul 06 '09 at 23:40
  • @Cambium: I understand if there is proprietary stuff... but I was having trouble understanding. Is your synchronized map NOT a concurrent collection (that comes standard in java)? Is internalMap just a regular map? Or is it a concurrent map? Or is it some proprietary class? – Tom Jul 06 '09 at 23:40
  • @finnw No, I am not modifying the internal map anywhere. The internal map is private and it can only be accessed via the three methods I provided. @ Tom The InternalMap class extends HashMap, but most of the methods (I used) are overriden (with the exception of keySet()) and protected with the synchronized keyword. – Cambium Jul 06 '09 at 23:47
  • Well, it's tough for me to tell if... but here's what I think the problem is. I cannot tell if all accesses to internalMap ultimately synchronize on the map. But I'm wondering if instead of a synchronized(this), you need a synchronized(internalMap). It's possible both may be needed. Ultimately, this seems like a bad design, but I imagine the error is coming from improper synchronization somewhere else. I think someone else has a handle to internalMap, and is using it while your are copying it. This is bad, and the easiest way to tell if this is the problem is to add synchronized(internalMap). – Tom Jul 07 '09 at 00:06
  • Hi Tom, thanks for the answers. I think I overlooked some stuff for the InternalMap design, and it actually extends LinkedHashMap (instead of HashMap) and sets accessOrder to true. While claimed to be "thread-safe", its behaviour differs from my expectations, which caused the exception to occur. I am trying to work around this atm. See Sean's answer: http://stackoverflow.com/questions/1089546/concurrent-modification-exception/1089727#1089727 – Cambium Jul 07 '09 at 00:11

12 Answers12

7

This exception does not normally have anything to do with synchronization - it is normally thrown if a a Collection is modified while an Iterator is iterating through it. AddAll methods may use an iterator - and its worth noting that the posh foreach loop iterates over instances of Iterator too.

e.g:

for(Object o : objects) {
    objects.remove(o);
}

is sufficient to get the exception on some collections (e.g ArrayList).

James

gub
  • 5,079
  • 3
  • 28
  • 33
  • 1
    Hi James, I fail to see how this is relevant. I don't think I am modifying the map/keyset of the map while iterating through it. – Cambium Jul 06 '09 at 23:42
  • @Cambrium - This is in fact the only way for this exception to occur - http://stackoverflow.com/questions/602636/concurrentmodificationexception-and-a-hashmap/602660#602660 – Robin Jul 07 '09 at 15:03
  • It is relevant because the exception is being thrown from your code almost certainly in the manner I describe. – gub Jul 07 '09 at 23:13
7

If you constructed LinkedHashMap with accessOrder = true then LinkedHashMap.get() actually mutates the LinkedHashMap since it stores the most recently accessed entry at the front of the linked list of entries. Perhaps something is calling get() while the array list is making its copy with the Iterator.

Sean McCauliff
  • 1,494
  • 1
  • 13
  • 26
  • Assuming no one calls get() while it's doing the copy, could this still trigger the exception? You raise a very good point though, I will look into the LinkedHashMap implementations. Thanks! – Cambium Jul 07 '09 at 00:14
  • Based on your comment above that you use accessOrder, and assuming you have to call get() within the rebuild function (since all you show is grabbing keys), this seems a likely culprit. Do the rebuild() calls ever overlap? – Michael Brewer-Davis Jul 07 '09 at 00:29
  • I imagine methods like containsKey() or containsValue() would also mutate the map, but the documentation for LinkedHashMap is silent on this issue. – Sean McCauliff Jul 07 '09 at 00:58
2

Are those all function you have in your wrapper? Because this exception could be thrown while you somehow iterating over collection in another place. And I guess you synchronized method with potential obvious race condition, but probably have missed less obvious cases. Here the reference to exception class docs.

Artem Barger
  • 40,769
  • 9
  • 59
  • 81
1

From the Javadoc:

If multiple threads access a linked hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. This is typically accomplished by synchronizing on some object that naturally encapsulates the map. If no such object exists, the map should be "wrapped" using the Collections.synchronizedMap method. This is best done at creation time, to prevent accidental unsynchronized access to the map:

Map m = Collections.synchronizedMap(new LinkedHashMap(...));

It may be safer for you to actually wrap the LinkedHashMap rather than claim you extend it. So your implementation would have an internal data member which is the Map returned by Collections.synchronizedMap(new LinkedHashMap(...)).

Please see the Collections javadoc for details: Collections.synchronizedMap

Tim Bender
  • 20,112
  • 2
  • 49
  • 58
0

Try removing the synchronized keyword from the set() and get() methods, and instead use a synchronized block inside the method, locking on internalMap; then change the synchronized blocks on the rebuild() method to lock on the internalMap as well.

Chochos
  • 5,155
  • 22
  • 27
  • Thanks for the suggestion, can you elaborate a little as to why that might solve the problem? I will give it a try shortly. – Cambium Jul 06 '09 at 22:54
  • Given that the internal map can only be accessed via the wrapper changing the lock will make no difference. You will just be moving text around for no benefit. – mP. Jul 07 '09 at 14:07
0

that is strange. I cant see any way that the exception is being thrown at the location you are identifying (in the default implmentation in Java 6). are you getting a ConcurrentModificationException in the ArrayList or the HashMap? have you overridden the keySet() method in your HashMap?

edit my mistake - the ArrayList will force the KeySet to iterate (AbstractCollection.toArray() will iterate over its keys)

there is somewhere in the code that is allowing you to update your internal map that is not synchronized.

  • do you have a utility method that exposes your internal map that "shouldnt be used" or
  • is your internal map identified as public scope
akf
  • 38,619
  • 8
  • 86
  • 96
  • No, keySet() is not overriden. I don't think this will cause a problem though because the only way to access this internal map is through the wrapper class's set(), get() and rebuild(). – Cambium Jul 06 '09 at 23:21
  • I completely agree with your analysis, however, I triple/quadruple checked my class, and made sure that those three methods are the only accessors of the internal map; and of course, internal map is private. – Cambium Jul 06 '09 at 23:50
0

Is internalMap static? You may have multiple objects, each locking on this, the object, but not providing correct locking on your static internalMap.

Stephen Denne
  • 36,219
  • 10
  • 45
  • 60
0

I don't think your set()/get() are synchronized against the same monitor that rebuild() is. This makes it possible for someone to call set/get while the problematic line is being executed, specifically when iterating over they key set of the internalMap during the addAll() call (internal implementation that's exposed through your stack trace).

Instead of making set() synchronized, have you tried something along the lines of:

public void set(SomeKey key, SomeValue val) {
    synchronized(this) {
        internalMap.put(key, val); // or whatever your get looks like
    }
}

I don't think you need to synchronize get() at all, but if you insist:

public SomeValue get(SomeKey key) {
    synchronized(this) {
        internalMap.get(key); // or whatever your get looks like
    }
}

In fact, I think you're better off synchronizing against internalMap instead of this. It also doesn't hurt to make internalMap volatile, though I don't think this is really necessary if you're sure that set()/get()/rebuild() are the only methods directly accessing internalMap, and they are all accessing it in a synchronized manner.

private volatile Map internalMap ...
Jack Leow
  • 21,945
  • 4
  • 50
  • 55
0

While iterating over the keys, due to

keysCopy.addAll(internalMap.keySet());

Do you decide that some of the entries can be removed from the LinkedHashMap?

The removeEldestEntry method could be either returning true, or modifying the map, thereby upsetting the iteration.

Stephen Denne
  • 36,219
  • 10
  • 45
  • 60
0

Try declaring the Map as transient

Reed
  • 11
  • 1
0

try this:

public void rebuild(){
  /* initialization stuff */
  List<SomeKey> keysCopy = new ArrayList<SomeKey>();
  synchronized (internalMap) {
    keysCopy.addAll(internalMap.keySet());
  }
  /* 
    do stuff with keysCopy, update a temporary map
   */    
  synchronized (internalMap) {
    internalMap.putAll(tempMap);
  }
}
Oso
  • 528
  • 4
  • 18
0

Keep access to internalMap syncronized, otherwise java.util.ConcurrentModificationException occurs because HashMap#modCount (which records structural changes) can be changed concurrently during keyset iteration (due keysCopy.addAll(internalMap.keySet() invocation).

LinkedHashMap javaDoc specifies : "In access-ordered linked hash maps, merely querying the map with get is a structural modification."