处理InterruptedException的同时等待退出信号(在Android的错误?) [英] Handling InterruptedException while waiting for an exit signal (bug in Android?)

查看:320
本文介绍了处理InterruptedException的同时等待退出信号(在Android的错误?)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经遇到了code以下,我想知道,如果它不正是我想象的那样:

I've come across the code below, and I'm wondering if it does exactly what I think it does:

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

关于上下文:还有另外一个线程来检查 mShouldExit (即的sObject 显示器内部),并退出在这种情况下。

About the context: there is another thread that checks for mShouldExit (inside the sObject monitor) and will exit in that case.

这不看是一个正确的方式给我。如果中断发生时,它会重新设定中断状态,所以当它返回到 sObject.wait(),另外一个InterruptedException的会等等,等等等等。因此,永远不能真正等待状态( sObject.wait()),即它永远不会释放的sObject 监测。这可能会导致无限循环,因为其他线程不能设置mExiting为真,因为它不能进入​​的sObject的监视器。 (所以我认为中断()电话是错误的,它不能在这里使用。)我失去了一些东西?

This does not look to be a correct pattern to me. If an interrupt happens, it will set the interrupted status again, so when it returns to sObject.wait(), another InterruptedException will come etc. etc. etc. Therefore, it can never go to truly waiting state (sObject.wait()) i.e. it will never release the sObject monitor. This may result in an infinite loop, as the other thread cannot set mExiting to true, because it can never enter sObject's monitor. (So I think that the interrupt() call is an error, it must not be used here.) Am I missing something?

请注意,code段是官方Android框架源$ C ​​$ C的一部分。

Note that the code snippet is a part of the official Android framework source code.

更新:实际上,情况更糟糕的是,因为同样的模式采用的是Android,当你的GL渲染开始。 GLSurfaceView.GLThread.surfaceCreated的官方消息code()

UPDATE: actually, the situation is worse, because the same pattern is used in Android when your GL rendering starts. The official source code of 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 状态,否则渲染将无法启动)。

You can reproduce the bug in a similar way: make sure your UI thread has its interrupted status flag yet, then add your GLSurfaceView and start the GL rendering (via setRenderer(...), but on some devices, make sure your GLSurfaceView has Visibility.VISIBLE status, otherwise rendering will not start).

如果你按照上面的步骤,你的UI线程最终会在一个无限循环,因为上面引述的code将继续产生 InterruptedException的(因以等待()),因此GL线程将永远无法设定 mWaitingForSurface 为false。

If you follow the above steps, your UI thread will end up in an infinite loop, because the above-quoted code will keep generating an InterruptedException (due to wait()) and therefore the GL thread will never be able to set mWaitingForSurface to false.

根据我的测试,似乎这样一个无限循环的也将导致GC_CONCURRENT垃圾回收(或者,至少,在logcat中这样的消息)的无限序列。有趣的是,有人对计算器一个未知的没有准确定义的问题,前面这可能与: 如何解决GC_concurrent释放?

According to my tests, it seems that such an infinite loop will also result in an endless sequence of GC_CONCURRENT garbage collection (or, at least, such messages in logcat). Interesting, someone had an unknown poorly-defined issue on stackoverflow earlier which might be related: How to solve GC_concurrent freed?

是不是有可能,或许是他的UI线程有其中断标志设置为true,并且他用一个GLSurfaceView因为他提到了地图?只是一个假设,一个可能的方案。

Isn't it possible that perhaps his UI thread had its interrupted flag set to true, and he was using a GLSurfaceView for the map he mentions? Just an assumption, a possible scenario.

推荐答案

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

Short version: That code is wrong, and will cause an infinite loop (I still have a doubt, but may depend on JVM implementations). Setting the interrupt status is the right thing to do, but it should then exit the loop, eventually checking that same interruption status using Thread.isInterrupted().

龙版为普通读者:

的问题是如何停止当前正在执行一些工作线程,响应于一个取消按钮从用户或者因为某些其它应用程序逻辑的

The problem is how to stop a thread that is currently executing some work, in response to a "Cancel" button from the user or because of some other application logic.

首先,Java的支持的停止的方法,即preemptively停止一个线程。这种方法已经被证明是不安全的,因没有给线程停止任何(易)的方式来清理,释放资源,避免暴露部分修改的对象等。

Initially, Java supported a "stop" method, that preemptively stopped a thread. This method has been demonstrated to be unsafe, cause didn't give the stopped thread any (easy) way to clean up, release resources, avoid exposing partially modified objects and so on.

所以,Java的发展,以合作主题中断制度。该系统非常简单:一个线程运行时,其他人所称的中断就可以了,一个标志设置的主题,它的主题责任,以检查是否已经中断或不并采取相应的

So, Java evolved to a "cooperative" Thread "interruption" system. This system is quite simple : a Thread is running, someone else calls "interrupt" on it, a flag is set on the Thread, it's Thread responsibility to check if it has been interrupted or not and act accordingly.

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

So, correct Thread.run (or Runnable.run, of Callable etc..) method implementation should be something like :

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

这是罚款,只要所有的code你的线程执行的run方法里,你永远不调用块很长一段时间......这往往并非如此,因为如果你产卵的东西一个线程是因为你有一些很长的事情。

This is fine as long as all the code your Thread is executing is inside your run method, and you never call something that blocks for a long time ... which is often not the case, cause if you spawn a Thread is because you have something long to do.

这是块是视频下载(millis)来最简单的方法,它实际上是它的唯一的事情:它可以阻止的时间给定数量的线程

The simplest method that block is Thread.sleep(millis), it's actually the only thing it does : it blocks the thread for the given amount of time.

现在,如果中断到来,而你的线程里面视频下载(6亿),没有任何其他的询问服务,它需要占用大量的资金到帐的地方它会检查isInterrupted点。

Now, if the interrupt arrives while your thread is inside Thread.sleep(600000000), without any other suport, it would take a lot for it to arrive to the point where it checks isInterrupted.

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

There are even situations where your thread would never exit. For example, your thread is computing something and sending results to a BlockingQueue with a limited size, you call queue.put(myresult), it will block until the consumer free some space in the queue, if in the mean time the consumer has been interrupted (or died or whatever), that space will never arrive, the method will not return, the check on .isInterrupted will never be performed, your thread is stuck.

要避免这种情况,所有的(多数)的方法中断线程(应该)抛出InterruptedException。这种异常只是告诉你我等待这做那,但在同时,一个线程被中断,你应该做的清理和退出,尽快。

To avoid this situation, all (most) methods that interrupt the thread (should) throw InterruptedException. That exception simply tells you "I was waiting for this and that, but in the meanwhile the thread as been interrupted, you should do cleanup and exit as soon as possible".

对于所有的异常,除非你知道该怎么做,你应该重新抛出它,并希望有人比你在调用栈知道。

As with all exceptions, unless you know what to do, you should re-throw it and hope that someone above you in the call stack knows.

InterruptedExceptions更差,因为当他们抛出的中断状态被清除。这意味着,只要捕捉和无视它们会引发一个线程,通常不会停止:

InterruptedExceptions are even worse, since when they are thrown the "interrupted status" is cleared. This means that simply catching and ignoring them will result in a thread that usually does not stop :

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

在这个例子中,如果睡眠()方法(它是时间99.9999999999%)在中断到达时,它会抛出InterruptedException,清除中断标志,那么循环将继续,因为中断标志是假的,该线程将不会停止。

In this example, if the interrupt arrives during the sleep() method (which is 99.9999999999% of the time), it will throw InterruptedException, clear the interrupt flag, then the loop will continue since the interrupt flag is false, and the thread will not stop.

这就是如果实现,为什么你的,而正确使用.isInterrupted,你真的需要捕捉InterruptedException的,和你没有什么特别的东西(如清理,恢复等),用它做,至少说明你能做的就是重新设置中断标志。

That's why if you implement your "while" correctly, using .isInterrupted, and you really need to catch InterruptedException, and you don't have anything special (like cleanup, return etc..) to do with it, least that you can do is set the interrupt flag again.

在您发布的code的问题是,而上mExited完全依赖于决定什么时候停止,不也isInterrupted。

The problem in the code you posted is that the "while" relies solely on mExited to decide when to stop, and not ALSO on isInterrupted.

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

或者,它可能退出时插话道:

Or it could exit when interrupted :

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

设置isInterrupted标志回真正的也是很重要的,如果你不控制线程。例如,如果你是在后者是为某种形式的线程池执行,或者任何地方的任何方法里面,你不拥有和控制线程(一个简单的例子:一个servlet)可运行的,你不知道中断是你(在servlet的情况下,客户端关闭了连接和容器试图阻止你释放其他请求的线程),或者如果它定位于螺纹(或系统)作为一个整体(的容器关闭,停止一切)。

Setting the isInterrupted flag back to true is also important if you don't control the Thread. For example, if you are in a runnable which is being executed in a thread pool of some kind, or inside any method anywhere you don't own and control the thread (a simple case : a servlet), you don't know if the interruption is for "you" (in the servlet case, the client closed the connection and the container is trying to stop you to free the thread for other requests) or if it's targeted at the thread (or system) as a whole (the container is shutting down, stopping everything).

在这种情况下(这是99%的code),如果不能重新抛出InterruptedException的(这是不幸的是,选中),传播了堆栈,该线程线程池的唯一途径被打断,被设置标志回真正的返回。

In that situation (which is 99% of the code), if you cannot rethrow the InterruptedException (which is, unfortunately, checked), the only way to propagate up the stack to the thread pool that the thread has been interrupted, is setting the flag back to true before returning.

这样一来,它会向上传播堆栈,最终产生更多InterruptedException的的,要由螺纹所有者(无论是JVM的执行器,或任何其他线程池本身),其可以适当的反应(重复利用螺纹,让死了,System.exit(1)...)

That way, it will propagate up the stack, eventually generating more InterruptedException's, up to the thread owner (be it the jvm itself, of an Executor, or any other thread pool) that can react properly (reuse the thread, let it die, System.exit(1) ...)

这其中大部分是覆盖在Java并发在实践中的第7章,一本很好的书,我推荐给任何人有兴趣在一般的计算机编程,而不仅仅是Java的,造成的问题和解决方案已经在许多其他环境类似,和说明都写得很好。

Most of this is covered in chapter 7 of Java Concurrency in Practice, a very good book that I recommend to anyone interested in computer programming in general, not just Java, cause the problems and the solutions are similar in many other environments, and explanations are very well written.

为什么太阳决定把InterruptedException的检查,当大多数文档建议无情地重新抛出它,为什么他们决定清除中断标志再次抛出该异常,当正确的做法是将其设置为true时,大部分时间仍然有待讨论。

Why Sun decided to make InterruptedException checked, when most documentation suggests to rethrow it mercilessly, and why they decided to clear the interrupted flag when throwing that exception, when the proper thing to do is setting it to true again most of the time, remains open for debate.

不过,如果.wait释放前检查中断标志锁,它从另一个线程开了一个小门,修改mExited布尔。不幸的是,wait()方法是本地人,使特定的JVM的来源应该进行检查。这不会改变的事实,您发布的code是codeD不佳。

However, if .wait releases the lock BEFORE checking for the interrupt flag, it open a small door from another thread to modify the mExited boolean. Unfortunately the wait() method is native, so source of that specific JVM should be inspected. This does not change the fact that the code you posted is coded poorly.

这篇关于处理InterruptedException的同时等待退出信号(在Android的错误?)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆