同步线程以避免 ArrayIndexOutOfBoundsException

发布于 2024-12-11 12:18:53 字数 1166 浏览 0 评论 0原文

我有两个单独的线程:

  • logicThread - 用于ai,运动,精灵排序和精灵销毁
  • drawThread - 用于绘图(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) {}

I have two seperate threads:

  • logicThread - for ai, movement, sprite sorting and destruction of sprites
  • drawingThread - for drawing (canvas.draw())

My logic thread calls ArrayList.remove(), so I would think when the drawing thread comes to draw there could be a chance of crashing because the index no longer exists.

code sample:

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);
        }
}}

Which would be the correct way to prevent an exception, syncronized or trycatch?
Also are there any benefits from using one over the other?

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

or

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

如梦 2024-12-18 12:18:53

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

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

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

Just for note: use iterators to remove items from collection:

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

Catching IndexOutOfBound exception is valid if it's acceptable. Another way - is to create array copy in drawing thread, which will guarantee that you don't get IndexOutOfBound. You can just add a check isDead() inside drawing loop

蘑菇王子 2024-12-18 12:18:53

您不应该捕获 ArrayIndexOutOfBoundsException ,因为这是一个未经检查的异常,这应该用于编程错误,并且仅在发生错误时抛出(请注意,这有点有争议,但上面是《Effective Java》,Bloch 告诉我们的)。

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

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

另请注意,如果在两个线程中使用共享对象时不同步它,则可能会产生奇怪的记忆效应(例如看到不完整的对象)。 Goetz 的《Java 并发实践》是一本很棒的书,它详细介绍了这个被广泛误解的概念。

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

You should not catch an ArrayIndexOutOfBoundsException, as this is an unchecked exception, this should be used for programming errors and only be thrown when one has occurred (note that this is a little bit controversial, but the above is what Effective Java, Bloch tells us).

If the action you are taking takes little time, use synchronization. If the loop takes a lot of time, use synchronization to copy the list and then iterate over the copy instead of the original.

Another problem you might run into (depending on the type of list you are using), is the ConcurrentModificationException, which occurs when you remove an element from a list that is being iterated.

Also note that if you do not synchronize shared objects when using it in the two threads, that you can have weird memory effects (such as seeing incomplete objects). Java Concurrency in practice by Goetz is a great book that teaches more on this widely misunderstood concept.

An alternative solution to using a synchronized block is using a CopyOnWriteArrayList, which will prevent the ConcurrentModificationException and the memory effects. Note that in order to use the 'copy effect' you need to use the iterators to the remove the elements.

毅然前行 2024-12-18 12:18:53

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

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

查看您的具体示例,我建议您可能不想这样做,因为您将需要在 for 循环周围保持锁定。您可能需要将同步移至共享 npc 对象中。

有什么原因你不能只在第一个线程中记下哪些 npc 已经死亡,然后在退出 for 循环后将它们从列表中删除。如果可以的话,最好避免单独的线程和同步。

You should absolutely synchronize access, but you need to do it in both threads, and you need to synchronize on the shared objects, e.g.

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

Looking at your specific example, I would suggest that you probably don't want do it this way since you will need to hold a lock around the for loop. You probably need to move the synchronization into the shared npc object.

Is there any reason you can't just note in the first thread which npcs have died, and then remove them from your list once you exit the for loop. It's much better to avoid a separate thread and the synchronization if you can.

一身仙ぐ女味 2024-12-18 12:18:53

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

Do you need threads for this? If you call the drawing loop followed by the logic loop (serially) do you get an adequate frame rate? Assuming you have a double buffered display. Threads should usually be used where there is something asynchronous going on (like waiting for a server to respond) which you can't control. In this case you have control of when and in which order things happen.

青柠芒果 2024-12-18 12:18:53

同步整个绘制/逻辑块将抵消线程的好处,并且捕获异常可能会导致 UI 不一致(更不用说更难管理代码)。

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

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

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

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

Synchronizing over the entire draw / logic blocks will negate the benefits of threading, and catching the exception could lead to inconsistent UI (not to mention harder to manage code).

When iterating over a collection that another thread might modify, copy it first!

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

You probably still need to synchronize the copy operation; the risks are far lower but there's still a race condition that will lead to nulls in your copy. Collections.synchronizedList() can turn a regular list into a synchronized list but at the expense of some speed on all operations.

If performance with synchronizedList() becomes a problem you can just manually synchronize the copy and remove operations.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文