3

我正在尝试遍历一个线程上的循环,如下所示:

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. 该列表本身是私有的,外部类无法直接检索它(有显式添加/删除对象的方法),所以我不希望这会影响结果。

4

2 回答 2

11

的意思

synchronized (c) {
    ... code that uses c ...
}

  • 等待c解锁
  • c
  • 执行身体
  • 开锁c

所以如果你在你的线程中同步,那么你的线程将等待c被解锁然后潜入。

现在,如果您同步修改 的另一个线程上的代码c,则该代码将继续进行修改c而无需等待锁定。在一个线程中同步块不会使另一个线程等待锁。如果另一个线程有一行如

c.add(someOtherTask)

那不在同步块中,那么无论如何它都会进行添加。这是您的异常的原因。这也是为什么即使您将代码放在同步块中的线程中也看到异常的原因:您的代码是“按规则播放”,但另一个线程却不在乎。

不过要小心同步长时间运行的代码。正如 Stephen C 所说,你最好使用并发集合类型。

于 2012-09-16T06:12:01.843 回答
4

同步不一定有帮助。

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

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


如果迭代和写入发生在不同的线程上,那么在迭代完成之前不应该同步阻塞写入吗?还是我错过了什么?

问题在于您如何实现同步:

  • 如果您没有在您的版本中明确进行某种同步LinkedList,则不会为您完成同步。

  • 如果您使用由其中一种Collections.synchronizedXxx方法创建的同步包装器,那么这些方法的 javadocs 会明确声明Iterator包装器方法返回的对象未iterator()同步。

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

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

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

于 2012-09-16T05:33:43.983 回答