在Java Concurrency in Practice中,有关于如何在线程中使用取消和中断的说明。此示例位于第7章“取消和关闭”的第21页,其中说明:
清单7.3。不可靠的取消可能导致生产者陷入阻塞操作。不要这样做。
他们告诉我们为了停止任何线程操作,只需创建一个可以检查的易失性标志。根据该标志的状态,线程执行停止。
现在有一个解释相同的程序。它在那里工作正常,下面是例子:
public class PrimeGenerator implements Runnable {
@GuardedBy("this")
private final List<BigInteger> primes = new ArrayList<BigInteger>();
private volatile boolean cancelled;
public void run() {
BigInteger p = BigInteger.ONE;
while (!cancelled) {
p = p.nextProbablePrime();
synchronized (this) {
primes.add(p);
}
}
}
public void cancel() {
cancelled = true;
}
public synchronized List<BigInteger> get() {
return new ArrayList<BigInteger>(primes);
}
List<BigInteger> aSecondOfPrimes() throws InterruptedException {
PrimeGenerator generator = new PrimeGenerator();
new Thread(generator).start();
try {
SECONDS.sleep(1);
} finally {
generator.cancel();
}
return generator.get();
}
}
在上面的代码cancelled
中是volatile标志,我们可以检查取消检查,如果真的话,线程执行会停止。
但是,如果我们执行上述操作,但使用BlockingQueue
则会出现问题。
但是,如果使用此方法的任务调用阻塞方法,例如
BlockingQueue.put()
我们可能会遇到更严重的问题,任务可能永远不会检查取消标记,因此可能永远不会终止。
BrokenPrimeProducer
说明了这个问题。生产者线程生成素数并将它们放在阻塞队列中。如果生产者领先于消费者,则队列将填满,put()
将阻止。如果消费者在put()
被阻止时尝试取消生产者任务,会发生什么?它可以调用cancel来设置cancelled
标志,但是生产者永远不会检查标志,因为它永远不会从阻塞put()
中出现(因为消费者已经停止从队列中检索质数)。
以下是相同的代码:
class BrokenPrimeProducer extends Thread {
private final BlockingQueue<BigInteger> queue;
private volatile boolean cancelled = false;
BrokenPrimeProducer(BlockingQueue<BigInteger> queue) {
this.queue = queue;
}
public void run() {
try {
BigInteger p = BigInteger.ONE;
while (!cancelled) {
queue.put(p = p.nextProbablePrime());
}
} catch (InterruptedException consumed) {
}
}
public void cancel() {
cancelled = true;
}
void consumePrimes() throws InterruptedException {
BlockingQueue<BigInteger> primes =...;
BrokenPrimeProducer producer = new BrokenPrimeProducer(primes);
producer.start();
try {
while (needMorePrimes()) {
consume(primes.take());
}
} finally {
producer.cancel();
}
}
}
我无法理解为什么在第二个代码示例中阻止Queue的情况下取消将不起作用。有人可以解释一下吗?
答案 0 :(得分:2)
这明确是因为BlockingQueue#put(E)
will block如果需要将值放在其中。由于代码处于阻塞状态,代码不能再次检查标志,因此在任何其他时间将标志设置为不同值的事实与当前阻塞的线程无关。
解决问题的唯一真正方法是中断线程,这将结束阻塞操作。
答案 1 :(得分:1)
当使用标志取消时,如果它已经开始休眠或等待,则无法让线程退出休眠或等待,而是必须等待休眠时间到期或等待以通知结束。阻塞意味着消费者线程处于等待状态,直到某些内容在空队列中排队,或者生产者线程处于等待状态,直到有空间将某些内容放入完整队列。被阻塞的线程永远不会离开等待方法 - 就好像你在睡眠或等待的线路上有一个断点,线程被冻结在该线路上,直到睡眠时间到期或线程收到通知为止(没有进入虚假的唤醒)。线程无法到达检查标志的行。
使用中断信号线程在等待或休眠时唤醒。你不能用旗帜做到这一点。
答案 2 :(得分:0)
需要检查取消标志。中断立即通知被阻止抛出InterruptedException
的线程,只有while循环的下一次迭代才会让线程知道它已被更改 - 也就是说,当线程解除阻塞并继续时。
看到问题?线程将不知道另一个线程是否设置了该标志。它被封锁了。它无法进入下一次迭代。
答案 3 :(得分:0)
needMorePrimes() 在某些情况下返回 false,然后消费者会调用 producer.cancel(),同时生产者将 BlockingQueue 填满,使其阻塞在 queue.put(p = p.nextProbablePrime() ) 并且无法查看取消状态,所以很糟糕。