在使用synchronized块的Object.wait()时,应该在哪里捕获InterruptedException?

7

据我了解,这是Java中常见的多线程片段。

boolean loaded = false;
Object lock = new Object();

public void block() {
    synchronized (lock) {
        while(!loaded)
            lock.wait(); // Unhandled exception type InterruptedException
    }
}

在另一个线程中。
public void done () {
    synchronized (lock) {
        loaded = true;
        lock.notify();
    }
}

但我不确定应该在哪里放置try和catch,是否可以将其包围整个同步块或仅仅是lock.wait()?有什么经验之谈吗?这是否真的很重要?

在处理时,在这种情况下调用Thread.currentThread().interrupt()是否可以?


1
A) 如果您的线程已经收到中断信号,最合理的做法是调用Thread.currentThread().interrupt()并退出。 B) 您应该使用高级同步原语 - 例如队列。 C) 您应该使用notifyAll而不是notify。 D) 您应该阅读一些关于多线程的书籍,比如Goetz的《Java并发编程实践》。 - Boris Treukhov
个人而言,当我使用这些机制(请参见@Boris Treukhov的评论)时,我会立即在wait()调用周围放置catch。 - radai
@BorisTreukhov,您能详细解释一下“但我不确定在堆栈中的所有方法上放置已检查异常是否是最佳解决方案。”这句话的意思吗?我不太明白它的含义。 - Nikolay Kuznetsov
Andrey的回答是正确的,但我认为使用throws关键字传播异常并不总是最好的选择,因为您可能希望执行一些关闭逻辑(我的意思是一些超出finallytry-with结构中仅仅关闭资源的操作)- 问题在于finally保留了其他异常情况(不仅仅是中断异常),而有些逻辑并不非常适合放在那里 - 因此客户端代码(及其流程)将变得不太可读。 - Boris Treukhov
重新表述一下 - 当您正在开发同步原语时,除了传播异常(原语没有任何其他逻辑)之外,您无法执行任何其他操作,但是当您使用throws进行多线程编写某些代码时,使用传播将强制您设计围绕捕获InterruptedException的逻辑 - 但是InterruptedException只有在JVM关闭时才会发生。 是的,有一些情况下,围绕异常设计程序是可以的(例如TimeoutException),但我认为这不是默认解决方案。 - Boris Treukhov
2个回答

6

你可能想考虑阅读Brian Goetz的文章:Java理论与实践:如何处理InterruptedException

一般情况下,如果你不知道该怎么做,那就什么都不要做,只需让调用者知道这个方法在等待锁时可能会被中断。你可以通过在方法签名中添加throws InterruptedException来实现。

如果由于某种原因,你被迫添加try/catch块并处理异常,那么Thread.currentThread().interrupt()是可行的,但重新抛出它也是可以的。


是的,我在写问题之前已经收藏了这篇文章。但我的主要问题用粗体标出。 - Nikolay Kuznetsov
@NikolayKuznetsov,实际上并不重要,由异常引起的程序流程变化与多线程无关。重要的是不能丢失中断状态。但我不确定在堆栈中的所有方法都放置已检查异常是否是最佳解决方案。另外,调用notify()而不是notifyAll()总是需要考虑安全性。 - Boris Treukhov

3
“但我不确定应该在哪里放置try和catch,我可以围绕整个同步块或只是lock.wait()吗?有什么经验法则并且这真的很重要吗?” lock.wait() 是您的 block 方法中唯一可能引发异常的一行代码。因此,无论您将 try/catch 放在 wait 周围还是包含整个方法体都不会影响执行(当然,假设您在两种情况下都退出循环 - 如果要继续循环,则 try/catch 块显然应该在循环内部)。
换句话说,这只是一个风格问题 - 参见 这个讨论 。我个人认为这种方式更易读:
try {
    synchronized (lock) {
        while (!loaded) {
            lock.wait(); // Unhandled exception type InterruptedException
        }
    }
} catch (InterruptedException e) {
    //to ignore me or not to ignore me, that is the question
    Thread.currentThread().interrupt();
}

在这种情况下,调用Thread.currentThread().interrupt()可以处理吗?

在下面的第一个示例中,如果捕获InterruptedException并退出循环而不重新中断线程,在任何情况下都可以,因为无论如何run方法都将退出,线程也将死亡:

new Thread(new Runnable() {

    @Override
    public void run() {
        block();
    }
}).start();

在下面的第二个示例中,你确实应该重新中断线程,以让run方法知道block()退出的原因是loaded变为true还是它被中断了:
public void run() {
    while(!Thread.currentThread().isInterrupted()) {
        loaded = false;
        System.out.println("Launching data load");
        block(); //if false has become true, loop, if interrupted, exit
    }
}

如果您不知道该方法将如何使用,良好的礼仪建议您重新中断线程。


我认为这个关于通知线程的教程更好:http://tutorials.jenkov.com/java-concurrency/thread-signaling.html - android developer

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