Tho*_*alc 9 java multithreading android interrupted-exception
我遇到过下面的代码,我想知道它是否完全符合我的想法:
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();
                }
            }
        }
    }
您可以用类似的方式重现错误:确保您的UI线程还有其中断状态标志,然后添加您的GLSurfaceView并启动GL渲染(通过setRenderer(...),但在某些设备上,确保您的GLSurfaceView具有Visibility.VISIBLE状态,否则渲染不会开始).
如果您按照上述步骤操作,您的UI线程将以无限循环结束,因为上面引用的代码将继续生成InterruptedException(由于wait()),因此GL线程永远不能设置mWaitingForSurface为false.
根据我的测试,似乎这样一个无限循环也会导致无休止的GC_CONCURRENT垃圾收集序列(或者至少是logcat中的这类消息).有趣的是,有人在之前的stackoverflow上有一个未知的定义不明确的问题可能是相关的: 如何解决GC_concurrent释放?
是不是可能他的UI线程将其中断标志设置为true,并且他正在使用GLSurfaceView作为他提到的地图?只是一个假设,一个可能的场景.
Sim*_*nni 16
简短版本:该代码是错误的,并将导致无限循环(我仍有疑问,但可能依赖于JVM实现).设置中断状态是正确的做法,但它应该退出循环,最终使用Thread.isInterrupted()检查相同的中断状态.
适合休闲读者的长版:
问题是如何停止当前正在执行某些工作的线程,响应用户的"取消"按钮或者由于某些其他应用程序逻辑.
最初,Java支持一种"停止"方法,该方法先发制人地停止了一个线程.此方法已被证明是不安全的,因为没有给停止的线程任何(简单)方法来清理,释放资源,避免暴露部分修改的对象等等.
因此,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)内,没有任何其他支持,它将需要很多才能到达它检查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,并且线程将不要停止
这就是为什么,如果你实现你的"而"正确使用.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(遗憾的是,检查过),那么将堆栈传播到线程已被中断的线程池的唯一方法就是设置在返回之前标记回true.
这样,它将向上传播堆栈,最终生成更多的InterruptedException,直到线程所有者(无论是jvm本身,Executor,还是任何其他线程池)都可以正常反应(重用线程,让它死掉, System.exit(1)...)
其中大部分内容都在Java Concurrency in Practice的第7章中介绍,这是一本非常好的书,我推荐给任何对计算机编程感兴趣的人,不仅仅是Java,导致问题和解决方案在许多其他环境中相似,并且解释是写得很好.
为什么Sun决定检查InterruptedException,当大多数文档建议无情地重新抛出它时,为什么他们决定在抛出异常时清除被中断的标志,当正确的事情是在大多数时间再次将其设置为true时,仍保持打开状态辩论.
但是,如果.wait在检查中断标志之前释放锁,它会从另一个线程打开一个小门来修改mExited布尔值.不幸的是,wait()方法是原生的,因此应该检查该特定JVM的源代码.这并不会改变您发布的代码编码不佳的事实.
| 归档时间: | 
 | 
| 查看次数: | 7627 次 | 
| 最近记录: |