简体   繁体   中英

ConcurrentModificationException on synchronized method

I'm processing a lot of events that come from a TCP socket (sets of ten per second), so I'm using multithreading to process those events.

public class MainActivity extends Activity {
  ...

  // In this Map I store the tab name and the associated TabHost.TabSpec instance
  private static Map<String, TabHost.TabSpec> Tabs = Collections.synchronizedMap(new LinkedHashMap<String, TabHost.TabSpec>());
  // In this Map I store pairs of tab-names and a HashSet of undelivered messages
  // It's a class that extends Map<String, HashSet<String>> with some additional functions that doesn't have anything to do with Tabs.
  private static NamesListing Names = Collections.synchronizedMap(new LinkedHashMap<String, HashSet<String>>());

  // Yes, I know the names don't follow the Java standards, but I keeped them for mantaining the question coherence. I will change it in my code, though.

  synchronized private static void ProcessEvent(final String name, final String message) {
    // Low-priority thread
    final Thread lowp = new Thread(
      new Runnable() { 
        public void run() {
          final Iterator<String> iter = Tabs.keySet().iterator();
          while (iter.hasNext()) {
            final String tabname = iter.next();
            // This just returns an int making some calculations over the tabname
            final int Status = Names.getUserStatus(tabname, message);

            // Same than getUserStatus
            if ((Names.isUserSpecial(Status)) && (name.equals(tabname))) {
              // This just removes a line from the HashSet
              Names.delLine(tabname, message);
            }
          }
        }
      });
    lowp.setPriority(3);
    lowp.start();
  }

  ...
}

Most of time this works right, but sometimes there are some avalanches of those events, and somethimes I get a ConcurrentModificationException:

12-10 14:08:42.071: E/AndroidRuntime(28135): FATAL EXCEPTION: Thread-369 12-10 14:08:42.071: E/AndroidRuntime(28135): java.util.ConcurrentModificationException 12-10 14:08:42.071: E/AndroidRuntime(28135): at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:347) 12-10 14:08:42.071: E/AndroidRuntime(28135): at java.util.LinkedHashMap$KeyIterator.next(LinkedHashMap.java:367) 12-10 14:08:42.071: E/AndroidRuntime(28135): at es.irchispano.chat.MainActivity$6.run(MainActivity.java:244) 12-10 14:08:42.071: E/AndroidRuntime(28135): at java.lang.Thread.run(Thread.java:841)

Note: The 244 line corresponds to the

final String tabname = iter.next();

statement.

It seems weird to me as I'm using a Collections.synchronizedMap and the method that processes those lines is synchronized, so why is it still happening?

Thanks!

---------- Edited ----------

Sorry for the concise initial code; I've tried to simplify as much as possible but obviously it was not a good idea. I'm pasting the actual code. Of course each of those structures are initialized (otherwise I wouldn't have a problem :-)), now I'm going to read all your comments with conscience and I'll post what I'll find out. Thank you all for the support!

The synchronized keyword in this example acquires a lock at the class MainActivity, then the method launches a new thread and releases the lock immediately.

There is no guarantee that the iteration of the first event is over before a new request is processed.

The new request could cause two threads to iterate simultaneously through the map.

If in method doSomeAdditionalStuff() there are modification operations on the map, this will cause one thread to modify the map while the other is still iterating through it, causing the ConcurrentModificationException.

You've shows us how map Tabs is created (Java naming conventions would dictate its name begin with a lowercase letter), but not how it is populated. As it is, the map would always be empty, and the while loop would run zero times. Also, the local variable tabname is unused, but presumably is not unused in your actual code.

That said, it appears that ProcessEvent will be run once for every event. It is static, and synchronized, which means that it will obtain the monitor for MainActivity.class, and no other method that synchronizes on the same object can run at the same time.

However, it starts a new thread, which does the actual work, and returns immediately. That thread is not synchronized, and so any number of these worker threads can be running at the same time, all using the same map. The map is wrapped with Collections.synchronizedMap and that is the only protection against concurrent modifications.

Such a synchronized map will not allow multiple callers to call its methods at the same time, but separate calls to different methods can be interleaved arbitrarily. For instance, while one caller is putting a new entry into the map, no other caller can access the map. However, it is possible for one caller to get the key set from the map, get the iterator from the key set, and start iterating over it, and then for another caller in another thread to add, modify, or remove an entry, and, finally, for the first thread to continue iterating over the key set and get a ConcurrentModificationException.

I would suggest using java.util.concurrent.ConcurrentHashMap instead, and removing the synchronized keyword from ProcessEvent.

Access to Tabs is not protected by any lock. The runnable.run() which has access to Tabs is also not protected by any lock. Your code allows the possibility to spawn multiple threads in parallel. Since, each thread's runnable has access to your map ( Tabs ), there exists the possibility for one of those threads to modify that map while another is iterating over it. This will result in the ConcurrentModificationException that you are seeing.

Using Collections.synchronizedMap only means that multiple threads can concurrently call get / put / delete on it. It does not prevent the error that occurs when a Collection changes while an Iterator is iterating over it. This can happen even within a single thread, for example the following thread should throw it if there are (I think) at least 3 elements in the map:

final Iterator<String> iter = Tabs.keySet().iterator();
iter.next(); 
String k = iter.next();
final Iterator<String> iter2 = Tabs.keySet().iterator();
iter2.next();
Tabs.delete(k);
iter2.next();

Now, as others have pointed out multiple threads can be iterating over the Map concurrently, since the run method is not synchronized. But if Tabs is not getting modified then that is not the source of the error.

You haven't shown where Tabs is getting modified. It is because the map is getting modified while the iterator is iterating over it that is causing the exception.

The fix is to iterate over it and modify it using the same lock.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM