我正在处理来自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并且处理这些行的方法是同步的,为什么它仍然会发生?
谢谢!
----------已编辑----------
对于简明的初始代码感到抱歉;我试图尽可能地简化,但显然这不是一个好主意。我正在粘贴实际的代码。当然,每个结构都被初始化(否则我不会有问题:-)),现在我要用良心阅读你所有的评论,我会发布我会发现的内容。谢谢大家的支持!
答案 0 :(得分:1)
此示例中的synchronized关键字获取MainActivity类的锁定,然后该方法启动一个新线程并立即释放锁定。
在处理新请求之前,无法保证第一个事件的迭代结束。
新请求可能导致两个线程同时在地图中迭代。
如果在方法doSomeAdditionalStuff()中有地图上的修改操作,这将导致一个线程修改地图而另一个线程仍在迭代它,导致ConcurrentModificationException。
答案 1 :(得分:1)
您已经向我们展示了如何创建map Tabs
(Java命名约定将指定其名称以小写字母开头),但不是如何填充它。实际上,地图总是空的,而while循环将运行零次。此外,局部变量tabname
未使用,但可能在您的实际代码中未使用。
也就是说,ProcessEvent
似乎每个事件都会运行一次。它是静态的,并且是同步的,这意味着它将获得MainActivity.class,
的监视器,并且在同一对象上同步的其他方法不能同时运行。
然而,它启动一个新线程,它执行实际工作,并立即返回。该线程未同步,因此任何数量的这些工作线程可以同时运行,所有这些都使用相同的映射。地图包含Collections.synchronizedMap
,这是防止并发修改的唯一保护。
这样的同步映射不允许多个调用者同时调用其方法,但可以任意交错对不同方法的单独调用。例如,当一个调用者将新条目放入映射时,没有其他调用者可以访问该映射。但是,一个调用者可以从映射中获取键集,从键集中获取迭代器,然后开始迭代它,然后在另一个线程中的另一个调用者添加,修改或删除条目,以及最后,第一个线程继续迭代密钥集并获得ConcurrentModificationException.
我建议改为使用java.util.concurrent.ConcurrentHashMap
,并从ProcessEvent.
答案 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}}被修改的位置。 这是因为当迭代器迭代它时导致异常的地图被修改。
修复方法是迭代它并使用相同的锁修改它。