清除 Thread.interrupt() 标志的方法

26
我最近接手了一个Java应用程序,它几乎没有任何线程安全性。我目前正在处理的问题是让所有线程正确地处理中断,而不是使用非常糟糕的Thread.stop()
问题的一部分是我不知道每个方法调用清除中断标志的操作。
目前我知道以下操作将清除中断标志:
Thread.interrupted()
Thread.sleep(long)
Thread.join()
Thread.join(long)
Object.wait()
Object.wait(long)

还有什么我错过的吗?谢谢

3个回答

53

问题的一部分在于我不知道有多少方法可以清除中断标志。

需要澄清的是,以下方法只需调用它们即可清除中断标志:

Thread.interrupted()
Thread.isInterrupted(true) -- added to your list

因此,应始终使用Thread.currentThread().isInterrupted()

以下方法将通过立即抛出InterruptedException来清除被中断的标志,无论它们是否被调用并且线程被中断,还是线程已经被中断然后它们被调用(请参见下面的junit代码)。因此,清除标志的不是方法本身,而是抛出异常。

您最初的列表:

Thread.interrupted()
Thread.sleep(long)
Thread.join()
Thread.join(long)
Object.wait()
Object.wait(long)

已添加至您的列表:

Thread.sleep(long, int)
Thread.join(int, long)
Thread.isInterrupted(true)
Object.wait(int, long)
BlockingQueue.put(...)
BlockingQueue.offer(...)
BlockingQueue.take(...)
BlockingQueue.poll(...)
Future.get(...)
Process.waitFor()
ExecutorService.invokeAll(...)
ExecutorService.invokeAny(...)
ExecutorService.awaitTermination(...)
CompletionService.poll(...)
CompletionService.take(...)
CountDownLatch.await(...)
CyclicBarrier.await(...)
Semaphore.acquire(...)
Semaphore.tryAcquire(...)
Lock.lockInteruptibly()
Lock.tryLock(...)

请注意,任何捕获 InterruptedException 的代码都应该立即重新中断线程。 我们这样做是为了防止其他人依赖于 thread.isInterrupted() 方法:

try {
    ...
} catch (InterruptedException e) {
    // immediately re-interrupt the thread
    Thread.currentThread().interrupt();
    // log the exception or [likely] quit the thread
}

下面是演示这些内容的JUnit代码:

assertFalse(Thread.currentThread().isInterrupted());
// you can do this from another thread by saying: someThread.interrupt();
Thread.currentThread().interrupt();
// this method does _not_ clear the interrupt flag
assertTrue(Thread.currentThread().isInterrupted());
// but this one _does_ and should probably not be used
assertTrue(Thread.interrupted());
assertFalse(Thread.currentThread().isInterrupted());
Thread.currentThread().interrupt();
assertTrue(Thread.currentThread().isInterrupted());
try {
    // this throws immediately because the thread is _already_ interrupted
    Thread.sleep(1);
    fail("will never get here");
} catch (InterruptedException e) {
    // and when the InterruptedException is throw, it clears the interrupt
    assertFalse(Thread.currentThread().isInterrupted());
    // we should re-interrupt the thread so other code can use interrupt status
    Thread.currentThread().interrupt();
}
assertTrue(Thread.currentThread().isInterrupted());

17

常见的约定是:任何抛出 InterruptedException(+ Thread.interrupted())的方法都会清除中断标志。

因此,为了使您的线程可中断,您需要找到所有捕获 InterruptedException 但没有重新抛出或恢复中断标志的地方。由于 InterruptedException 是一个已检查异常,所以这并不难做到。


那是我进行的代码库的第一次遍历,但我面临的情况是之前的程序员会捕获一个通用异常而不是InterruptedException。 - OverflowingStack

2

以下是一个非常有趣的例子:

在1.1.4版本之前,ch.qos.logback.core.AsyncAppenderBase会捕获并吃掉中断异常,但不会重置线程标志。

因此,如果您使用任何路由到此日志记录器(如slf4j)的内容,它将悄悄地吞噬您的线程中断状态。因为,我是说,在每个可能的日志操作之前和之后,谁不会检查线程中断状态呢?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接