这个问题更多的是关于询问我的做事方式是否是“正确”的方式。我有一些程序涉及不断更新图形组件。为此,我有以下方法。
public void update(){
for (BaseGameEntity movingEntity : movingEntityList) {
((MovingEntity)movingEntity).update();
}
}
本质上,包含此方法的类包含所有需要更新的图形对象的列表,并循环遍历,调用各自的更新方法。
当我必须添加新实体或从此列表中删除当前实体时,问题就出现了。实体的添加和删除由不同的线程处理,正如您所猜测的,如果我尝试添加/删除实体同时循环并更新其图形组件,则会导致并发修改异常。
我的临时解决方案是简单地在此周围抛出一个try-catch块,并忽略突然出现的任何并发修改异常 - 实际上,不在该特定时间更新。这正是我想要的,没有问题发生。
public void update(){
try{
for (BaseGameEntity movingEntity : movingEntityList) {
((MovingEntity)movingEntity).update();
}
}catch(ConcurrentModificationException e){
//Do Nothing
}
}
但是,我的问题是,这是处理这个问题的“正确”方法吗?我是否应该做类似this answer中概述的事情?如果我的错了,处理这个问题的“正确”方法是什么?我并没有专门寻找使我的arraylist线程安全的方法,例如通过同步列表,我特别询问我的方法是否是一个有效的方法,或者是否有某些原因我应该避免它并实际使用同步列表。 / p>
答案 0 :(得分:2)
正确的方法是将列表与Collections.synchronizedList()
:
List list = Collections.synchronizedList(new ArrayList());
...
synchronized (list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
如果您的浏览次数超过更新列表的次数,您还可以使用CopyOnWriteArrayList
。
如果您不介意偶尔丢失更新(或者如果它们的同步价格很少发生),您的方式就可以了。
答案 1 :(得分:1)
这是处理此问题的“正确”方法吗?
如果你不介意以降低错误更新为代价来增加并发性,那么答案就是“是”。如果经常发生对列表的重大添加和删除,您确实存在连续多次未完成更新的风险。
另一方面,当更新频率明显高于添加/删除对象的频率时,这种解决方案听起来很合理。
我应该[使用
synchronized
]吗?
这也是一个可行的解决方案。不同之处在于更新正在进行时更新将无法继续。当调用update
的时间很关键时,这可能是不可取的(但在每次调用时更新所有内容并不重要。)
答案 2 :(得分:1)
有些人认为它是所有通用同步问题的副本。我认为情况并非如此。在这个意义上,你要求一个非常具体的星座,以及你的解决方案是否“正常”。
根据您的描述,实际目标似乎很明确:您希望快速并发地迭代实体以调用update
方法,并避免使用{{{{}}隐含的任何同步开销1}}或类似的方法。
此外,我认为您提出的解决方案背后的主要思想是Collections#synchronizedList
调用必须经常且尽可能快地完成,而添加或删除实体“很少”发生。
因此,与常规操作相比,添加和删除元素是异常; - )
并且(正如his answer中已经指出的那样)这样的设置,捕获和忽略update
的解决方案是合理的,但是你应该知道后果。
可能会调用某些实体的ConcurrentModificationException
方法,然后由于update
而导致循环失效。您应绝对确定,这不会产生不良副作用。例如,根据ConcurrentModificationException
实际执行的操作,这可能导致某些实体在屏幕上移动得更快,而其他实体则根本不移动,因为他们的update
次调用由于多次update
而被错过{1}}。如果添加和删除实体不很少发生的操作,这可能会特别成问题:如果一个线程不断添加或删除元素,那么列表的最后一个元素可能永远不会收到ConcurrentModificationExceptions
打电话。
如果你想要一些“通过例子证明”:我首先在JUNG Graph Library中遇到过这种模式,例如,在SpringLayout类和其他人中。当我第一次看到它时,我畏缩了一下,因为乍一看它看起来非常黑暗和危险。但在这里,理由是相同的:过程必须尽可能快,并且对图结构的修改(这将导致异常)很少见。请注意,当update
发生时,JUNG的人实际上会对相应的方法进行递归调用 - 仅仅是因为他们不能总是假设该方法被另一个线程不断调用。反过来,这个会产生令人讨厌的副作用:如果另一个线程进行常量修改,并且每次调用该方法时都会抛出 ,,那么这将以ConcurrentModificationException
结束......但幸运的是,情况并非如此。