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()调用是一个错误,它不能在这里使用.)我错过了什么吗?
更新:实际上,情况更糟,因为在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(); } } } }
您可以以类似的方式重现错误:确保您的UI线程已经处于中断状态标志,然后添加您的GLSurfaceView并启动GL渲染(通过setRenderer(…),但在某些设备上,确保您的GLSurfaceView具有Visibility.VISIBLE状态,否则渲染将无法启动).
如果您遵循上述步骤,您的UI线程将最终导致无限循环,因为上述代码将不断产生InterruptedException(由于wait()),因此GL线程将永远无法将mWaitingForSurface设置为false .
根据我的测试,似乎这样一个无限循环也将导致GC_CONCURRENT垃圾收集(或至少在logcat中的这种消息)的无序序列.有趣的是,有人在早期的stackoverflow中有一个未知的不明确的问题,可能与之相关:
How to solve GC_concurrent freed?
也许他的UI线程的中断标志设置为true,他是否可能使用GLSurfaceView来提及他的地图?只是一个假设,一个可能的情况.
解决方法
长版本为休闲读者:
问题是如何阻止正在执行某些工作的线程,以响应用户的“取消”按钮或由于某些其他应用程序逻辑.
最初,Java支持一种“停止”方法,它预先停止了一个线程.这种方法已经被证明是不安全的,因为没有给停止的线程任何(简单)的方法来清理,释放资源,避免暴露部分修改的对象等等.
所以,Java演变成一个“合作”的线程“中断”系统.这个系统很简单:一个线程正在运行,有人在其上调用“中断”,线程上设置了一个标志,它的线程负责检查它是否被中断,并且相应地执行.
所以,正确的Thread.run(或Runnable.run,Callable等)的方法实现应该是这样的:
public void run() { while (!Thread.getCurrentThread().isInterrupted()) { // Do your work here // Eventually check isInterrupted again before long running computations } // clean up and return }
只要您的线程正在执行的所有代码都在您的run方法中,那么这是很好的,你永远不会调用长时间阻塞的东西,这通常不是这样,因为如果你产生一个线程是因为你有要做很多事情
阻塞的最简单的方法是Thread.sleep(millis),它实际上是它唯一的做法:它在给定的时间内阻止线程.
现在,如果你的线程在Thread.sleep(600000000)内部的中断到达,没有任何其他的支持,它将需要很多到达它检查isInterrupted的点.
甚至在你的线程永远不会退出的情况下.例如,您的线程正在计算某些东西并将结果发送到具有有限大小的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,线程将不停止
这就是为什么如果你正确地实现“while”,使用.isInterrupted,并且你真的需要捕获InterruptedException,并且你没有任何特殊的东西(如清理,返回等),至少你可以做再次设置中断标志.
您发布的代码中的问题是“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的情况下,客户端关闭了连接,容器试图阻止你释放线程以用于其他请求),或者如果它的目标是整个线程(或系统)容器正在关闭,停止一切).
在这种情况下(这是代码的99%),如果不能重新抛出InterruptedException(不幸的是被检查),那么将堆栈传播到线程被中断的线程池的唯一方法是设置返回前返回真标.
这样,它将传播到堆栈,最终生成更多的InterruptedException,直到线程所有者(无论是jvm本身,执行程序或任何其他线程池),可以正常响应(重用线程,让它死亡,System.exit(1)…)
大部分内容在Java并发实践的第7章中有介绍,这是一本非常好的书,我向任何对计算机编程感兴趣的用户(不仅仅是Java)推荐,因为这些问题和解决方案在许多其他环境中都是类似的,解释是写得很好
为什么Sun决定对InterruptedException进行检查,当大多数文档建议无情推翻它,为什么他们决定在抛出异常时清除中断的标志,当正确的做法是将其设置为true时,大部分时间都会保持打开状态供辩论.
但是,如果.wait释放锁定在检查中断标志之前,它将从另一个线程打开一个小门来修改mExited boolean.不幸的是,wait()方法是本机的,因此应该检查该特定JVM的源.这不会改变你发布的代码编码不好的事实.