Java并发编程 - 中断策略

4

我正在阅读 《Java并发编程实践》,在章节“Cancellation and Shutdown”中提到:

一个任务不应假设它所执行的线程的中断策略,除非它明确地设计为在具有特定中断策略的服务中运行。无论任务将中断解释为取消还是采取其他一些动作,它都应当小心地保留执行线程的中断状态。如果它不会将 InterruptedException 传播给其调用者,那么它在捕获 InterruptedException 后应恢复中断状态:Thread.currentThread().interrupt()

所以,我尝试调整列表样例来理解这段话。但是,输出结果让我感到困惑。

PrimeProducer

public class CorrectPrimeProducer extends Thread {

    private final BlockingQueue<BigInteger> queue;

    public CorrectPrimeProducer(BlockingQueue<BigInteger> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName()+" interrupt status in producer:" + Thread.currentThread().isInterrupted());
            BigInteger p = BigInteger.ONE;
            while (!Thread.currentThread().isInterrupted()) {
                queue.put(p = p.nextProbablePrime());
            }
        } catch (InterruptedException e) {
            /* Allow thread to exit */
            Thread.currentThread().interrupt();
            System.out.println(Thread.currentThread().getName()+" interrupt status in producer catch:" + Thread.currentThread().isInterrupted());
        }
    }
}

主函数##

public static void main(String[] args) throws InterruptedException {
        BlockingQueue<BigInteger> primes = new LinkedBlockingQueue<>();
        CorrectPrimeProducer generator = new CorrectPrimeProducer(primes);
        generator.start();
        try {
            while (needMorePrimes()) {
                consume(primes.take());
            }
        } finally {
            generator.interrupt();
        }
        TimeUnit.SECONDS.sleep(5);
        System.out.println(generator.getName()+" interrupt status in main:"+generator.isInterrupted());
    }

    //do something
    private static void consume(BigInteger take) {
        System.out.println(take);
    }

    private static int counter = 1;

    private static boolean needMorePrimes() {
        counter++;
        if(counter == 10){
// after counter reaches 10 return false
            return false;
        }
        return true; 
    }

输出:

// when TimeUnit.SECONDS.sleep(5); in main class is not commented

Thread-0 interrupt status in producer:false
2
3
5
7
11
13
17
19
Thread-0 interrupt status in producer catch:true
Thread-0 interrupt status in main:false

//When TimeUnit.SECONDS.sleep(5); in main class is commented
Thread-0 interrupt status in producer:false
2
3
5
7
11
13
17
19
Thread-0 interrupt status in main:true
Thread-0 interrupt status in producer catch:true

问题

  1. 在主类的主线程中添加 TimeUnit.SECONDS.sleep(5) 就能重置执行线程(即生成器)的中断状态。如果我注释掉 TimeUnit.SECONDS.sleep(5) 方法,那么中断状态将保留。为什么会出现这种情况?如何解决?

  2. 书中提到一个线程应该只被它的拥有者中断。在上面的例子中,谁是拥有者?我认为是主方法线程。


如果你的代码调用了任何库代码,那么就算你不应该假设任何关于中断策略的事情,但是你怎么知道库作者没有假设中断意味着整个程序正在关闭,他们可以放弃正在进行的工作,并且不清理工作现场就return或者throw?除非他们明确记录下他们的库如何处理中断(这几乎从未发生),否则你是不知道的,或者,你可以直接和作者交流。我对使用中断来除了关闭进程之外的其他任何目的都感到非常不安。 - Solomon Slow
2个回答

3
在主类的主线程中添加 TimeUnit.SECONDS.sleep(5)。执行线程(即生成器)的中断状态会被重置。如果我注释掉 TimeUnit.SECONDS.sleep(5) 方法,则在这种情况下保留中断状态。为什么会发生这种情况?如何解决?
除了阻塞队列之外,您没有在主线程和 CorrectPrimeProducer 之间使用任何同步机制,因此当主线程打印状态时,CorrectPrimeProducer 可能尚未通过执行 catch 块指令来保留中断状态,因此您会得到 false 结果。
当您将 sleep 添加到主线程时,只是增加了 CorrectPrimeProducer 线程通过调用 catch 块指令保存中断状态的可能性,然后才尝试打印其状态。这就是为什么它打印 true 的原因。
书中提到,线程应仅由所有者中断。在上面的示例中,所有者是谁?我认为是主方法线程。
在这种情况下,您是 CorrectPrimeProducer 线程的所有者(所有者是创建线程的代码),因此您可以决定对其进行什么样的中断处理。例如,如果被中断,则可以重新创建它(这通常发生在默认情况下从 Java 线程池中获取的线程中)。

谢谢。但是如果使用sleep,那么主线程会将CorrectPrimeProducer线程的中断状态打印为false。 - balboa_21

1
通过添加 TimeUnit.SECONDS.sleep(5),您为线程提供了足够的时间来终止。
当线程终止时,它的中断标志被清除。
这在规范中没有记录,但这就是发生的情况。例如,参见 this bug report

这里没有违反任何规范,因此我已将其作为增强请求而不是错误。可以说,缺乏规范是一个错误 - 我们有意指定“终止后的中断不需要影响”来处理中断状态存储在 VM 中并且一旦线程终止就不存在的事实。但是我们忽略了在 Thread.isInterrupted 规范中反映这一点。

如果没有额外的 sleep,理论上你可能会看到 truefalse 的中断状态,因为存在竞争条件,但由于线程调度,更有可能会看到 true。在抛出异常和在 catch 块中恢复中断状态之间的中断状态为 false 的时间窗口非常小。

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