DDukesterman
DDukesterman

Reputation: 1399

ConcurrentModificationException in a runnable

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)

Upvotes: 0

Views: 1003

Answers (2)

Andrei Mankevich
Andrei Mankevich

Reputation: 2283

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();
    }
}

Upvotes: 4

cybersam
cybersam

Reputation: 67044

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.

Upvotes: 0

Related Questions