在等待退出信号时处理InterruptedException(Android中的错误)

我遇到了下面的代码,我想知道它是否确实如我所想:

synchronized(sObject) { mShouldExit = true; sObject.notifyAll() while (!mExited) { try { sObject.wait(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } 

关于上下文:还有另一个线程检查mShouldExit (在sObject监视器内)并在这种情况下退出。

这对我来说并不是一个正确的模式。 如果中断发生,它会再次设置中断状态,所以当它返回到sObject.wait() ,会有另一个InterruptedException等等等等,所以永远不能进入真正的等待状态( sObject.wait() )即它永远不会释放sObject监视器。 这可能会导致无限循环,因为其他线程无法将mExiting设置为true,因为它永远不能进入sObject的监视器。 (所以我认为interrupt()调用是一个错误,它不能在这里使用。)我错过了什么?

请注意,代码片段是官方Android框架源代码的一部分。

更新:实际上,情况更糟糕,因为当您的GL呈现开始时,Android中使用相同的模式。 GLSurfaceView.GLThread.surfaceCreated()的官方源代码:

  public void surfaceCreated() { synchronized(sGLThreadManager) { if (LOG_THREADS) { Log.i("GLThread", "surfaceCreated tid=" + getId()); } mHasSurface = true; sGLThreadManager.notifyAll(); while((mWaitingForSurface) && (!mExited)) { try { sGLThreadManager.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } 

你可以用类似的方法重现这个bug:确保你的UI线程有中断状态标志,然后添加你的GLSurfaceView并开始GL渲染(通过setRenderer(...) ,但是在一些设备上,确保你的GLSurfaceView有Visibility.VISIBLE状态,否则渲染不会启动)。

如果你按照上面的步骤,你的UI线程将会以无限循环结束,因为上面引用的代码将继续生成InterruptedException (由于wait() ),因此GL线程将永远不能将mWaitingForSurface设置为false 。

根据我的testing,似乎这样一个无限循环也将导致无休止的GC_CONCURRENT垃圾回收序列 (或者至lesslogcat中的这种消息)。 有趣的是,有人有一个未知的stackoverflow较早定义的问题可能是相关的: 如何解决GC_concurrent释放?

是不是有可能他的UI线程的中断标志设置为true,并且他正在使用他提到的地图的GLSurfaceView? 只是一个假设,一个可能的情况。

Solutions Collecting From Web of "在等待退出信号时处理InterruptedException(Android中的错误)"

简短版本:该代码是错误的,并会导致无限循环(我仍然有疑问,但可能依赖于JVM实现)。 设置中断状态是正确的,但它应该退出循环,最终使用Thread.isInterrupted()检查相同的中断状态。

长版的休闲读者:

问题是如何停止正在执行某些工作的线程,以响应用户的“取消”button或其他应用程序逻辑。

最初,Java支持“停止”方法,抢先停止线程。 这种方法已经被certificate是不安全的,因为没有给停止的线程清理任何(简单的)方法,释放资源,避免暴露部分被修改的对象等等。

所以,Java演变成一个“合作”的线程“中断”系统。 这个系统很简单:一个线程正在运行,另一个线程正在调用“中断”,在线程上设置了一个标志,线程负责检查线程是否被中断,并相应地执行。

所以,正确的Thread.run(或Callable等..的Runnable.run)方法实现应该是这样的:

 public void run() { while (!Thread.getCurrentThread().isInterrupted()) { // Do your work here // Eventually check isInterrupted again before long running computations } // clean up and return } 

只要你的Thread执行的所有代码都在你的run方法中,并且永远不会调用阻塞很长一段时间的东西,情况往往不是这样,因为如果你产生了一个Thread,要做很长的事情

最简单的方法是Thread.sleep(millis),它实际上是唯一的方法:在给定的时间内阻塞线程。

现在,如果你的线程在Thread.sleep(600000000)之内,而没有任何其他的支持,那么如果中断到达,它将花费很多时间来检查isInterrupt。

甚至有线程永远不会退出的情况。 例如,你的线程正在计算一些东西,并将结果发送到一个大小有限的BlockingQueue,你调用queue.put(myresult),它将阻塞,直到消费者释放队列中的一些空间,如果消费者已经中断(或死亡或其他),该空间将永远不会到达,方法将不会返回,.isInterrupted检查将永远不会执行,您的线程被卡住。

为了避免这种情况,所有(大多数)中断线程的方法(应该)抛出InterruptedException。 那个例外只是告诉你:“我正在等待这个,那个线程被中断,你应该尽快清理并退出”。

和所有例外情况一样,除非你知道该怎么做,否则你应该重新抛出它,并希望在堆栈中的某个人以上的人知道。

InterruptedExceptions甚至更糟,因为当它们被抛出时,“中断状态”被清除。 这意味着简单地捕捉和忽略它们将导致通常不会停止的线程:

 public void run() { while (!Thread.getCurrentThread().isInterrupted()) { try { Thread.sleep(1000); } catch (InterruptedException e) { // Nothing here } } } 

在这个例子中,如果中断在sleep()方法(99.9999999999%)期间到达,它将抛出InterruptedException,清除中断标志,然后循环将继续,因为中断标志为false,并且线程将不停止。

这就是为什么如果你正确地使用.isInterrupt来实现你的“while”,而且你真的需要捕获InterruptedException,并且你没有什么特别的东西(比如清理,返回等等)来处理它,至less你可以做再次设置中断标志。

你发布的代码中的问题是,“while”完全依赖mExited来决定何时停止,而不是在isInterrupted上。

 while (!mExited && !Thread.getCurrentThread().isInterrupted()) { 

或者可以在中断时退出:

 } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; // supposing there is no cleanup or other stuff to be done } 

如果您不控制线程,则将isInterrupted标志设置为true也很重要。 例如,如果你在一个正在执行的runnable中,或者在任何你不拥有和控制线程的任何方法内部执行(一个简单的例子:一个servlet),你不知道是否中断是“你”(在servlet的情况下,客户端closures连接,容器试图阻止你释放线程的其他请求),或者如果它是针对整个线程(或系统)集装箱正在closures,停止一切)。

在这种情况下(这是99%的代码),如果不能重新抛出InterruptedException(不幸的是,选中),将堆栈传播到线程池已被中断的唯一方法是设置在返回之前将其标记为真。

这样,它将传播堆栈,最终生成更多的InterruptedException,直到线程所有者(无论是Executor或任何其他线程池的jvm本身)能够正确地作出反应(重用线程,让它死亡, System.exit(1)…)

大部分内容在Java Concurrency in Practice第7章中有介绍,我推荐给那些对计算机编程感兴趣的人,不仅仅是Java,这个问题很多,而且解决scheme在很多其他环境中是相似的,解释是写得很好。

为什么Sun决定检查InterruptedException,大多数文档build议无情地重新抛出它,为什么他们决定在抛出exception的时候清除中断的标志,当正确的事情是在大多数时候再次将其设置为true时,仍然是打开的辩论。

但是,如果.wait在检查中断标志之前释放锁,它会从另一个线程打开一个小门来修改mExited布尔值。 不幸的是,wait()方法是本地的,所以应该检查特定JVM的源代码。 这不会改变您发布的代码编码不佳的事实。