从HashMap中删除项目会产生ConcurrentModificationException

时间:2016-10-14 08:53:10

标签: java arrays multithreading

我有一个服务器应用程序,它有一个连接的ArrayList。该程序使用多个线程。主线程每隔一段时间运行一个进程以检测已关闭的连接,然后将它们从数组中删除,以便它们可以被垃圾收集。

此过程如下:

private void cullOtherProcessors() throws Exception {
    //log.log(Level.FINE,"XMLMessageReciever:cullOtherProcessors");
    List<ConnectionAppInterface> toDel = new ArrayList<ConnectionAppInterface>();
    for (ConnectionAppInterface cur : m_OtherProcessors.keySet()) {
        if (cur!=null) {
            if (cur.isClosed()) {
                //Connection is closed - we could never send a message over it
                toDel.add(cur);
            }
        }
    }
    for (int c=0;c<toDel.size();c++) {
        log.log(Level.FINE,"**XMLMessageReciever:cullOtherProcessors - removing closed connection");
        m_OtherProcessors.remove(toDel.get(c));
    }
}

我的服务器运行了几个月,但根据日志,它崩溃了以下错误:

08/10/16 01:06:39     calling connect
08/10/16 01:06:39   **XMLMessageReciever:cullOtherProcessors - removing closed connection
08/10/16 01:06:39   CloseableThread: End of run for thread Socket.Connect(113726)
08/10/16 01:06:39     Checking connected
08/10/16 01:06:39     Active Threads this group: 5
08/10/16 01:06:39     getting Message Reciever
08/10/16 01:06:39     Active Threads: 8
08/10/16 01:06:39     Setting m_establishingCon
08/10/16 01:06:39     Establishing connection to robertsNode
Server Failed
java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
    at java.util.HashMap$KeyIterator.next(HashMap.java:1453)
    at metcarob.com.common.network.xmlprotocol.XMLMessageReciever.cullOtherProcessors(XMLMessageReciever.java:57)
    at metcarob.com.common.network.xmlprotocol.XMLMessageReciever.LoopIteration(XMLMessageReciever.java:98)
    at metcarob.com.common.network.xmlprotocol.ConnectionManager.LoopIteration(ConnectionManager.java:48)
    at metcarob.com.personalservices.singlenodeserver.Main.run(Main.java:138)
    at metcarob.com.personalservices.singlenodeserver.Main.main(Main.java:398)

基本上,当我循环遍历它时(可能是另一个连接被建立),ArrayList发生了一些事情,导致nextNode抛出异常。

我正在努力找出解决问题的最佳方法。我正在考虑简单地捕捉并忽略错误。任何错过的线程都会在下一个循环中被剔除。我提出的解决方案是:

private void cullOtherProcessors() throws Exception {
    //log.log(Level.FINE,"XMLMessageReciever:cullOtherProcessors");
    //m_OtherProcessors
    List<ConnectionAppInterface> toDel = new ArrayList<ConnectionAppInterface>();
    try {
        for (ConnectionAppInterface cur : m_OtherProcessors.keySet()) {
            if (cur!=null) {
                if (cur.isClosed()) {
                    //Connection is closed - we could never send a message over it
                    toDel.add(cur);
                }
            }
        }
    } catch (ConcurrentModificationException e) {
        log.log(Level.FINE,"**XMLMessageReciever:cullOtherProcessors - ConcurrentModificationException being ignored");
    }
    for (int c=0;c<toDel.size();c++) {
        log.log(Level.FINE,"**XMLMessageReciever:cullOtherProcessors - removing closed connection");
        m_OtherProcessors.remove(toDel.get(c));
    }
}

我现在将这段代码放回服务器并运行几个月。

我想知道这是否是解决问题的好方法。由于首先发生这种情况需要花费数月的时间,并且代码每5分钟循环一次,我认为它发生的可能性很小,因此连接永远不会被剔除。 (我会看日志,看看)

了解专家的想法会很好。

**有人建议这个问题和&#34;迭代一个Collection,在循环中删除时避免使用ConcurrentModificationException&#34;

不是。在我的代码中,我不会在迭代HashMap时删除项目,而是迭代地图并存储我打算在临时数组中删除的项目。然后我浏览数组并删除项目。

4 个答案:

答案 0 :(得分:1)

HashMap 不是线程安全的集合,这意味着您不会想要与多个线程共享它,否则您将难以像ConcurrentModificationException那样重现错误或由于memory barriers

抓住ConcurrentModificationException显然是一种不好的做法,绝不应该这样做,因为必须将其视为HashMap的警卫,如果您的地图同时被修改,最终会导致您通知您破碎的地图和/或更糟的数据丢失。

您应该使用包java.util.concurrent中的线程安全集合。如果您需要线程安全Map,最好选择使用ConcurrentHashMap

答案 1 :(得分:0)

答案 2 :(得分:0)

嗨,好像你在循环它时修改了m_OtherProcessors。

有两种解决方案比捕捉我能想到的更好。

1-如果m_OtherProcessors是一个小对象并且mem不是问题,您可以只创建对象的本地副本,例如HashMap&lt; ...&gt; copy = new HashMap&lt; ..&gt;(m_OtherProcessors)。并使用此对象运行您的功能。 (如果你想避免在复制过程中发生崩溃的可能性,你很可能在复制之前插入同步语句,如synchronized(m_OtherProcessors){HashMap&lt; ...&gt; copy = new HashMap&lt; ..&gt;(m_OtherProcessors )})

2 - 您刚刚同步了方法或arrayList,这种同步会导致性能下降但可靠性更高。

答案 3 :(得分:0)

在遍历/读取集合期间执行数据修改时会导致并发修改异常。

一种最容易预防的方法;使用iterator遍历collection

离。

Iterator<String> iter = myArrayList.iterator();

    while (iter.hasNext()) {
        String str = iter.next();

        if (someCondition)
            iter.remove();
    }