同步线程以避免ArrayIndexOutOfBoundsException

时间:2011-10-25 09:49:40

标签: java android multithreading

我有两个独立的主题:

  • logicThread - 用于ai,移动,精灵排序和精灵的破坏
  • drawingThread - 用于绘图(canvas.draw())

我的逻辑线程调用ArrayList.remove(),所以我认为当绘图线程绘制时,可能会有机会崩溃,因为索引不再存在。

代码示例:

drawingThread extends Thread {
    logicThread = new LogicThread;
    logicThread.start();

    public void run(){
        while(running) {
            for(int i=0; i<npc.size(); i++){
                npc.get(i).callDraw();
            }
        }
        // stop logicThread when out of gameloop
        logicThread.running = false;
}}

LogicThread extends Thread {
    public void run(){
        while(running){
            for(int i=0; i<npc.size();i++){
                if(npc.get(i).isDead()){
                    npc.remove(i);
                }
                npc.trimToSize();
            }
            Collection.sort(npc);
        }
}}

哪种方法可以防止异常同步 trycatch ? 使用其中一个还有什么好处吗?

synchronized(logicThread) { 
    for(int i=0; npc.size(); i++) {
        npc.callDraw(); 
}}

try {
    npc.callDraw();
} catch(Exception e) {}

5 个答案:

答案 0 :(得分:3)

仅供注意:使用迭代器从集合中删除项目:

while (it.hasNex()) {
  if(...) {
    it.remove();

如果可接受,则捕获IndexOutOfBound异常有效。另一种方法 - 在绘图线程中创建数组副本,这将保证您不会获得IndexOutOfBound。你可以在绘图循环中添加一个检查isDead()

答案 1 :(得分:1)

您应该绝对同步访问权限,但您需要在两个线程中执行此操作,并且需要在共享对象上进行同步,例如

synchronized(npc) {
   // Do something that accesses or modifies npc
}

看看你的具体例子,我建议你可能不希望这样做,因为你需要锁定for循环。您可能需要将同步移动到共享的npc对象中。

是否有任何理由你不能在第一个线程中注意哪些npcs已经死亡,然后在退出for循环后将其从列表中删除。如果可以的话,最好避免单独的线程和同步。

答案 2 :(得分:1)

你不应该抓住ArrayIndexOutOfBoundsException,因为这是一个未经检查的异常,这应该用于编程错误,只有在发生错误时抛出(注意这有点争议,但上面是什么有效的Java,布洛赫告诉我们)。

如果您正在采取的操作花费的时间很少,请使用同步。如果循环花费大量时间,请使用同步来复制列表,然后迭代副本而不是原始副本。

您可能遇到的另一个问题(取决于您使用的列表类型)是ConcurrentModificationException,当您从正在迭代的列表中删除元素时会发生这种情况。

另请注意,如果在两个线程中使用共享对象时不同步共享对象,则可能会产生奇怪的内存效果(例如查看不完整的对象)。 Goetz实践中的Java Concurrency是一本很好的书,它更多地讲述了这个被广泛误解的概念。

使用synchronized块的另一种解决方案是使用CopyOnWriteArrayList,它将阻止ConcurrentModificationException和内存效果。请注意,为了使用“复制效果”,您需要使用迭代器来删除元素。

答案 3 :(得分:1)

你需要线程吗?如果您调用绘图循环后跟逻辑循环(串行),您是否获得足够的帧速率?假设你有一个双缓冲显示器。通常应该在异步进行的地方使用线程(比如等待服务器响应),这是你无法控制的。在这种情况下,您可以控制事情发生的时间和顺序。

答案 4 :(得分:1)

在整个绘图/逻辑块上进行同步将否定线程的好处,捕获异常可能会导致UI不一致(更不用说管理代码了)。

当迭代另一个线程可能修改的集合时,先复制它!

List drawList = new ArrayList(npc);
for(int i=0; i<drawList.size(); i++){
    drawList.get(i).callDraw();
}

您可能仍需要同步复制操作;风险要低得多,但仍存在竞争条件,导致副本中出现空白。 Collections.synchronizedList()可以将常规列表转换为同步列表,但会牺牲所有操作的速度。

如果使用synchronizedList()的性能成为问题,您只需手动同步复制和删除操作即可。