我有一个服务器应用程序,它有一个连接的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时删除项目,而是迭代地图并存储我打算在临时数组中删除的项目。然后我浏览数组并删除项目。
答案 0 :(得分:1)
HashMap
不是线程安全的集合,这意味着您不会想要与多个线程共享它,否则您将难以像ConcurrentModificationException
那样重现错误或由于memory barriers
。
抓住ConcurrentModificationException
显然是一种不好的做法,绝不应该这样做,因为必须将其视为HashMap
的警卫,如果您的地图同时被修改,最终会导致您通知您破碎的地图和/或更糟的数据丢失。
您应该使用包java.util.concurrent
中的线程安全集合。如果您需要线程安全Map
,最好选择使用ConcurrentHashMap
。
答案 1 :(得分:0)
使用synchronizedList会更好: https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedList-java.util.List-
答案 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();
}