【问题标题】:Java LinkedList iterator and Thread Safety for game with RenderThread and UpdateThread带有 RenderThread 和 UpdateThread 的游戏的 Java LinkedList 迭代器和线程安全
【发布时间】:2014-03-28 18:20:28
【问题描述】:

我在一个有 2 个线程、一个渲染线程和一个更新线程的 Android 应用程序中有一个共享的 java LinkedList。两个线程都在持续运行并不断迭代共享的 LinkedList。有时更新线程必须从 LinkedList 中删除一个对象。我如何对程序进行编码以保持多线程性能但允许这种情况发生。我不断从渲染线程收到 java.util.ConcurrentModificationException,因为我猜想在我尝试渲染它时该对象被删除了。

请注意,我在我的代码中使用了迭代器...

public void run()
{
  if(Thread.currentThread() == renderThread)
  {
    while(true)
    {
      for(Iterator<GameObject> it = objects.iterator(); it.hasNext();)
      {
         it.next().render();
      }
    }
  }
  if(Thread.currentThread() == updateThread)
  {
    while(true)
    {
      for(Iterator<GameObject> it = objects.iterator(); it.hasNext();)
      {
         GameObject o = it.next();

         it.next().update();

         if(o.shouldBeRemoved())
             it.remove();
      }
    }
  }
}

一种可能的解决方案是捕获异常并忽略它,但我觉得这是骗子的出路,因为异常应该用于异常情况,而不是流。

【问题讨论】:

  • 只需锁定(synchronizedGameObject(不是整个列表),每个线程都在使用它。
  • 您应该在列表上创建一个锁,因为那可能会遇到麻烦。即在迭代时从列表中删除一个元素。另外,我建议您使用 Synchronized 来锁定列表对象,而不是像上面那样进行线程比较。
  • 或者你也可以使用线程安全列表。
  • 我可以在您的代码中看到许多不同的缺陷:一种多用途方法用于两种不同的任务;共享状态的不正确同步;错误使用迭代器;两个线程都在繁忙的循环中运行......为什么实际上有两个不同的线程用于更新和渲染?只使用一个线程按顺序执行这两项操作。这可能不是最高效的解决方案,但它可以让您免于头疼!
  • @Matthew 您是否已经遇到过性能问题?您是否使用分析器来查找瓶颈?如果你想通过线程将更新和渲染分开,同步对象列表只是一件事。因为两个线程也会访问对象,所以它们的状态也必须同步。如果您不以巧妙的方式执行此操作(例如,通过使用受线程限制的快照),您将不会获得太多性能,而是会拥有一个复杂且容易出错的系统。当然是可行的,但我认为它需要更高的并发经验。

标签: java android multithreading linked-list thread-safety


【解决方案1】:

你是modifying your list structurally更新线程中,同时,渲染线程创建了一个迭代器并且玩得很开心迭代你的对象。您会收到ConcurrentModificationException 作为未处理同步问题的惩罚。

尝试使用 同步块/方法wait()/notify() 搜索任何示例。

UPD:好的,这是对您的代码的简单修改,它不会引发异常。这不是 List 同步的一个很好的例子,你没有获得太多的性能(更新和渲染操作仍然在不同的线程中,但是按顺序执行),并且需要考虑的事情很少 - 如果任何其他线程试图修改你的 List,你仍然会得到一个并发异常。

在你的启动器类中定义两个线程:

private Thread t1, t2;

启动线程的代码(例如,inside public static void main(..)):

final List<GameObject> list = initList();//some initialization
t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            myMultipurposeMethod(list);
        }
    });

 t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            myMultipurposeMethod(list);
        }
    });
 t1.start();
 t2.start();

现在是 GameObject 类的简化版本。注意同步块:

class GameObject {

    private int id;
    private static int count = 0;
    private boolean shouldDelete = false;
    private Object monitor = new Object();//will be used to lock critical section

    public GameObject() {
        id = ++count;
    }


    public void update(Thread thread) {
        synchronized (monitor) {
            shouldDelete = Math.random() < 0.5;//just a simple randomization
            System.out.println(thread.getName() + " updates GameObject. should be removed =" + shouldBeRemoved());
        }
    }

    public boolean shouldBeRemoved() {
        synchronized (monitor) {
            return shouldDelete;
        }
    }

    public void render(Thread thread) {
        System.out.println(thread.getName() + " renders GameObject " + id);
    }
}

最后,你的多用途方法:

public void myMultipurposeMethod(List<GameObject> ls) {
    synchronized (ls) {
        if (Thread.currentThread().equals(t1)) {
            while (true) {                    
                for (Iterator<GameObject> i = ls.iterator(); i.hasNext(); ) {
                    i.next().render(Thread.currentThread());
                    ls.notify();
                }
                try {
                    ls.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } else if (Thread.currentThread().equals(t2)) {
            while (true) {
                for (Iterator<GameObject> i = ls.iterator(); i.hasNext(); ) {
                    GameObject o = i.next();
                    o.update(Thread.currentThread());
                    if (o.shouldBeRemoved()) {
                        i.remove();
                        System.out.println("game object with id=" + o.id + " marked as garbage and was removed");
                    }
                }
                ls.notify();

                try {
                    ls.wait();
                } catch (InterruptedException e) {
                    e.printStackTract();
                }
            }
        }
    }
}

如果您遵循序言中给出的要求,这会给您一种并行执行的错觉并防止您的异常发生。但是,为了获得真正的性能提升,您最好遵循 cmets 中提供给 OP 的 @isnot2bad 建议。

【讨论】:

  • 此代码适用于“连续”运行的游戏。从 2 个不同的线程调用 run 方法,然后每个线程都进入它们各自的循环。 (我会担心在终止游戏时退出线程)
  • 感谢代码和帮助。从您的解释中可以看出,这是一个多线程应用程序,由于等待和通知,它基本上就像一个单线程应用程序。那是对的吗?如果是这样,似乎最好只使用一个线程?
  • 不完全是。这仍然是一个多线程应用程序,并且渲染/更新发生在主线程之外。但是,如果您希望遵循您在 OP 中提出的策略,您最终会导致这两个线程一个接一个地工作。我不能向您提出最佳解决方案,但我想,如果:在 update 线程中您会选择,如果您的对象应该被渲染或删除,并且在 render 线程,您将渲染或移除对象。然后,您将只在一个线程中从结构上修改您的列表,并且您可以调度具有不同优先级的线程。
  • 感谢您的帮助!非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-07
  • 1970-01-01
  • 1970-01-01
  • 2017-04-15
  • 2012-03-06
  • 2011-12-13
相关资源
最近更新 更多