同步方法的ConcurrentModificationException

时间:2013-12-10 19:36:32

标签: java android multithreading

我正在处理来自TCP套接字的大量事件(每秒十个),所以我使用多线程来处理这些事件。

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

  ...
}

大部分时间这都是正常的,但有时会发生一些事件的雪崩,有些时候我会得到一个ConcurrentModificationException:

  

12-10 14:08:42.071:E / AndroidRuntime(28135):致命异常:   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)

注意:244行对应

final String tabname = iter.next();

语句。

对我来说这似乎很奇怪,因为我正在使用Collections.synchronizedMap并且处理这些行的方法是同步的,为什么它仍然会发生?

谢谢!

----------已编辑----------

对于简明的初始代码感到抱歉;我试图尽可能地简化,但显然这不是一个好主意。我正在粘贴实际的代码。当然,每个结构都被初始化(否则我不会有问题:-)),现在我要用良心阅读你所有的评论,我会发布我会发现的内容。谢谢大家的支持!

4 个答案:

答案 0 :(得分:1)

此示例中的synchronized关键字获取MainActivity类的锁定,然后该方法启动一个新线程并立即释放锁定。

在处理新请求之前,无法保证第一个事件的迭代结束。

新请求可能导致两个线程同时在地图中迭代。

如果在方法doSomeAdditionalStuff()中有地图上的修改操作,这将导致一个线程修改地图而另一个线程仍在迭代它,导致ConcurrentModificationException。

答案 1 :(得分:1)

您已经向我们展示了如何创建map Tabs(Java命名约定将指定其名称以小写字母开头),但不是如何填充它。实际上,地图总是空的,而while循环将运行零次。此外,局部变量tabname未使用,但可能在您的实际代码中未使用。

也就是说,ProcessEvent似乎每个事件都会运行一次。它是静态的,并且是同步的,这意味着它将获得MainActivity.class,的监视器,并且在同一对象上同步的其他方法不能同时运行。

然而,它启动一个新线程,它执行实际工作,并立即返回。该线程未同步,因此任何数量的这些工作线程可以同时运行,所有这些都使用相同的映射。地图包含Collections.synchronizedMap,这是防止并发修改的唯一保护。

这样的同步映射不允许多个调用者同时调用其方法,但可以任意交错对不同方法的单独调用。例如,当一个调用者将新条目放入映射时,没有其他调用者可以访问该映射。但是,一个调用者可以从映射中获取键集,从键集中获取迭代器,然后开始迭代它,然后在另一个线程中的另一个调用者添加,修改或删除条目,以及最后,第一个线程继续迭代密钥集并获得ConcurrentModificationException.

我建议改为使用java.util.concurrent.ConcurrentHashMap,并从ProcessEvent.

中删除synchronized关键字

答案 2 :(得分:1)

Tabs的访问权限不受任何锁定的保护。有权访问runnable.run()的{​​{1}}也不受任何锁定的保护。您的代码允许并行生成多个线程。因为每个线程的runnable都可以访问你的地图(Tabs),所以其中一个线程可能会修改该地图,而另一个线程则迭代它。这将导致您看到的Tabs

答案 3 :(得分:1)

仅使用Collections.synchronizedMap意味着多个线程可以同时在其上调用get / put / delete。当Collection迭代Iterator时,它不会阻止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(); 更改时发生的错误。这甚至可以在单个线程中发生,例如,如果(我认为)地图中至少有3个元素,则后面的线程应该抛出它:

Map

现在,正如其他人指出的那样,多个线程可以同时迭代run,因为Tabs方法不同步。但是如果Tabs未被修改,那么这不是错误的来源。

您尚未显示{{1}}被修改的位置。 这是因为当迭代器迭代它时导致异常的地图被修改。

修复方法是迭代它并使用相同的锁修改它。