在foreach循环中同步仍会引发ConcurrentModificationException

时间:2012-09-16 05:21:31

标签: java concurrency synchronized

我正在尝试在一个线程上迭代循环,如下所示:

for (UnitTask task : chain) {
    g.drawLine((int) task.getLocation().getX(), (int) task.getLocation().getY(), (int) currentPos.getX(), (int) currentPos.getY());
    g.fillOval((int) task.getLocation().getX() - 2, (int) task.getLocation().getY() - 2, 5, 5);
    currentPos = task.getLocation();
}

但是,我有另一个可以添加到此对象的线程(Swing事件线程)。因此,ConcurrentModificationException。我试着用synchronized (chain) { ... }包围代码来获取锁定,但我仍然得到错误。

作为一个Java同步新手,我有点困惑为什么。我希望这可以使循环线程安全,但显然,它不是。

有趣的是,chain是自定义类的一个实例,但它只是LinkedList的一个薄包装器。列表本身是私有的,外部类没有办法直接检索它(有明确添加/删除对象的方法),所以我不希望这会影响结果。

2 个答案:

答案 0 :(得分:10)

的含义
synchronized (c) {
    ... code that uses c ...
}

  • 等待c解锁
  • 锁定c
  • 执行正文
  • 解锁c

因此,如果您在线程中进行同步,那么您的线程将等待c解锁,然后潜入。

现在,如果你同步修改c其他线程上的代码,那么该代码将继续并修改{{ 1}}无需等待锁定。在一个线程中同步块不会使另一个线程等待锁定。如果另一个线程有一行,如

c

不在同步块中,无论如何都会添加。这是您的例外原因。这也是为什么你看到异常的原因,即使你把代码放在你的线程中的同步块:你的代码是“按规则播放”,但另一个线程不能少关心。

请注意同步长时间运行的代码。正如Stephen C所说,你最好使用并发集合类型。

答案 1 :(得分:3)

同步不一定有帮助。

基本上问题是您使用的集合类型在迭代进行时不允许修改集合(除非通过迭代器的remove方法...如果支持) 。这不是线程/同步问题本身。 (如果你试图通过同步来解决它,你可能会引入另一个问题。)

如果您希望能够同时进行迭代和修改,则需要使用其他集合类型,例如ConcurrentLinkedDeque而不是LinkedList


  

如果迭代和写入发生在不同的线程上,那么不应该同步阻止写入直到迭代完成?或者我错过了什么?

问题在于如何实现同步:

  • 如果您未在LinkedList版本中明确进行某种同步,则不会为您执行任何同步。

  • 如果您使用由Collections.synchronizedXxx方法之一创建的同步包装器,那么这些方法的javadoc清楚地表明包装器返回的Iterator对象iterator() {{ 1}}方法未同步。

  • 如果您正在手动执行同步,则必须确保所有内容都在同一个互斥锁上进行同步。并且该锁必须在迭代期间保持在该互斥锁上...而不仅仅是调用iterator()

请注意,如果长时间持有锁(例如,在迭代长列表时),这可能会阻止需要长时间更新列表的其他线程。这种事情可能是并发瓶颈,可能(在最坏的情况下)将系统性能降低到单个处理器的速度。

ConcurrentXxx类通常通过放宽迭代器生成的序列的一致性保证来避免这种情况。例如,在开始迭代后,您可能看不到添加到集合中的元素。