2

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!

nKn
  • 13,691
  • 9
  • 45
  • 62
  • 3
    Does `doSomeAdditionalStuff()` modify the map? – rgettman Dec 10 '13 at 19:38
  • ^ Because that is the key :) – Martin Marconcini Dec 10 '13 at 19:38
  • @rgettman nope, it doesn't do anything to the map – nKn Dec 10 '13 at 19:41
  • Is processevent called by multiple threads ? – Mac Dec 10 '13 at 19:44
  • @Mac its private, so probably not –  Dec 10 '13 at 19:45
  • @Mac ProcessEvent is called within a MainActivity attached Handler that receives the socket Messages from an AsyncTask – nKn Dec 10 '13 at 19:50
  • @RC. I haven't done programming in android but I guess that multiple instance MainActivity is creating. And they are calling processevent method themselves which share the same instance of map as it is static.. – Mac Dec 10 '13 at 19:52
  • We would be able to help you better if you put the code for `dosomeadditionalstuff` – Mac Dec 10 '13 at 19:58
  • The map instance is static so thst could be the root of your problem...but frankly it's not possible to get the point without seeing the relevant part of that activity. Looking at the code it seems you are iterating over an empty map – Andrea Dec 10 '13 at 19:59
  • 1
    I believe there will only be a single instance of MainActivity, but it will start multiple worker threads which can stomp on one another. – David Conrad Dec 10 '13 at 19:59
  • 1
    Come on guys this question doesn't deserve downvote.. Give some air to the newbies.. +1 to counterbalance – Mac Dec 10 '13 at 20:03
  • Just Don't spawn a new thread... – Mac Dec 10 '13 at 20:35
  • See also http://stackoverflow.com/questions/1655362/concurrentmodificationexception-despite-using-synchronized – Raedwald Jan 22 '16 at 15:58

4 Answers4

4

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.

David Conrad
  • 15,432
  • 2
  • 42
  • 54
  • 1
    You hit the nail on the head, thanks to this complete explaination I was able to make some additional debugging and found out that this exception is happening exactly when processing another kind of event, which ADDS a new tab in background. So I'm going to make the needed changes keeping in mind all the valuable responses in this thread. I've upvoted all the useful comments but this one helped me the most so I'm accepting it. Thanks guys! – nKn Dec 10 '13 at 21:01
1

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.

Angular University
  • 42,341
  • 15
  • 74
  • 81
1

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.

cmd
  • 11,622
  • 7
  • 51
  • 61
1

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.

Miserable Variable
  • 28,432
  • 15
  • 72
  • 133