0

I am developing a timer manager that will allow multiple countdown timers and I cant seem to figure out how to avoid this ConcurrentModificationException. I have look at other peoples responses to similar problems but still cant figure it out.

mHandler = new Handler();

        mUpdateUI = new Runnable() {
            public void run() {

        Iterator<HashMap.Entry<String, TimerHolder>> it = mTimers.entrySet().iterator();
            while (it.hasNext()) {
   -----------> Map.Entry<String, TimerHolder> pairs = it.next();
                pairs.getValue().post();
            }

            Iterator<HashMap.Entry<String, TimerHolder>> iterator = mTimers.entrySet().iterator();
            while (iterator.hasNext()) {
                HashMap.Entry<String, TimerHolder> entry = iterator.next();
                if (!entry.getValue().isValid()) {
                   iterator.remove();
                }
            }                   


                mHandler.postDelayed(mUpdateUI, 1000); // 1 second
            }
        };
        mHandler.post(mUpdateUI);

06-02 12:37:23.746: E/AndroidRuntime(10669): FATAL EXCEPTION: main
06-02 12:37:23.746: E/AndroidRuntime(10669): java.util.ConcurrentModificationException
06-02 12:37:23.746: E/AndroidRuntime(10669):    at java.util.HashMap$HashIterator.nextEntry(HashMap.java:806)
06-02 12:37:23.746: E/AndroidRuntime(10669):    at java.util.HashMap$EntryIterator.next(HashMap.java:843)
06-02 12:37:23.746: E/AndroidRuntime(10669):    at java.util.HashMap$EntryIterator.next(HashMap.java:841)
06-02 12:37:23.746: E/AndroidRuntime(10669):    at com.watcher.timer.TimerManager$1.run(TimerManager.java:57)
06-02 12:37:23.746: E/AndroidRuntime(10669):    at android.os.Handler.handleCallback(Handler.java:730)
06-02 12:37:23.746: E/AndroidRuntime(10669):    at android.os.Handler.dispatchMessage(Handler.java:92)
06-02 12:37:23.746: E/AndroidRuntime(10669):    at android.os.Looper.loop(Looper.java:137)
06-02 12:37:23.746: E/AndroidRuntime(10669):    at android.app.ActivityThread.main(ActivityThread.java:5493)
06-02 12:37:23.746: E/AndroidRuntime(10669):    at java.lang.reflect.Method.invokeNative(Native Method)
06-02 12:37:23.746: E/AndroidRuntime(10669):    at java.lang.reflect.Method.invoke(Method.java:525)
06-02 12:37:23.746: E/AndroidRuntime(10669):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1209)
06-02 12:37:23.746: E/AndroidRuntime(10669):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1025)
06-02 12:37:23.746: E/AndroidRuntime(10669):    at dalvik.system.NativeStart.main(Native Method)
DDukesterman
  • 1,391
  • 5
  • 24
  • 42
  • http://www.journaldev.com/378/how-to-avoid-concurrentmodificationexception-when-using-an-iterator – Raghunandan Jun 02 '14 at 17:44
  • you need to use an iterator instead of a foreach loop. – meh Jun 02 '14 at 18:03
  • Long story short, mTimers is being modified somewhere in your code while looping on either of these calls: for (HashMap.Entry entry : mTimers.entrySet()) – Martin Cazares Jun 02 '14 at 18:18
  • On both foreach loops? @meh – DDukesterman Jun 02 '14 at 18:52
  • you cannot remove any item from the list when using a foreach loop on that list – meh Jun 02 '14 at 20:16
  • 1
    Possible duplicate of [Iterating through a Collection, avoiding ConcurrentModificationException when removing in loop](http://stackoverflow.com/questions/223918/iterating-through-a-collection-avoiding-concurrentmodificationexception-when-re) – Raedwald Mar 28 '16 at 14:50

2 Answers2

4

Actually your issue is not related to concurrency. Your Runnable is always called from the same thread - the Main thread, because you post it to Handler.

ConcurrentModificationException is thrown because you tries to modify collection inside for-each loop. That's fail-fast behaviour in order to protect non-thread-safe collections from potential concurrent modifications. You need to use Iterator explicitly and call remove at iterator object. Your "deletes invalid entries" part should look like this:

Iterator<HashMap.Entry<String, TimerHolder>> iterator = mTimers.entrySet().iterator();
while (iterator.hasNext()) {
    HashMap.Entry<String, TimerHolder> entry = iterator.next();
    if (!entry.getValue().isValid()) {
       iterator.remove();
    }
}
Andrei Mankevich
  • 2,253
  • 16
  • 15
0

Some other thread is modifying the mTimers Map at the same time that your run() method is iterating through the same Map.

One possible (but not necessarily most efficient) way to work around this is to wrap the current Map instance in a synchronized map, and use the wrapped object everywhere:

Map mTimers = Collections.synchronizedMap(new HashMap(...));

A synchronized map will synchronize the Map methods for you, so that only a single thread at a time can use the Map.

However, if you need to call multiple Map methods in a single atomic transaction, you will still need to do your own synchronization.

cybersam
  • 63,203
  • 6
  • 53
  • 76