Обычный способ - прерывать потоки, но это, конечно, требует, чтобы они правильно обрабатывали прерывания.
Это означает, что нужно правильно ловить и обрабатывать InterruptedException
вокруг методов блокировки, а в противном случае регулярно проверять (и действовать) флаг interrupted
.
В спецификации API или языка нет ничего, что связывало бы прерывание с какой-либо конкретной семантикой отмены, но на практике использование прерывания для чего угодно, кроме отмены, хрупко и трудно поддерживать в больших приложениях. [...]
Прерывание, как правило, является наиболее разумным способом осуществления отмены.
Говорит Параллелизм Java на практике в разделе 7.1.1. Пример правильной обработки прерывания из того же самого (это поток производителя, а не потребитель, но это различие незначительно в текущем контексте):
class PrimeProducer extends Thread {
private final BlockingQueue<BigInteger> queue;
PrimeProducer(BlockingQueue<BigInteger> queue) {
this.queue = queue;
}
public void run() {
try {
BigInteger p = BigInteger.ONE;
while (!Thread.currentThread().isInterrupted())
queue.put(p = p.nextProbablePrime());
} catch (InterruptedException consumed) {
/* Allow thread to exit */
}
}
public void cancel() { interrupt(); }
}
Альтернативным решением было бы установить разумно низкий параметр тайм-аута poll
, чтобы поток регулярно просыпался и мог достаточно быстро замечать прерывания. Тем не менее, я считаю, что всегда полезно явно обрабатывать InterruptedException в соответствии с вашей политикой отмены потоков.