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.