Simultaneidade Java - Políticas de Interrupção

Aug 26 2020

Estou lendo Java Concurrency in Practice . Na seção Políticas de Interrupção no capítulo

Cancelamento e desligamento

é mencionado

Uma tarefa não deve assumir nada sobre a política de interrupção de seu thread em execução, a menos que seja explicitamente projetada para ser executada em um serviço que tenha uma política de interrupção específica. Quer uma tarefa interprete a interrupção como cancelamento ou execute alguma outra ação na interrupção, ela deve tomar cuidado para preservar o status de interrupção do thread em execução. Se não for propagar InterruptedException para seu chamador, ele deve restaurar o status de interrupção após capturar InterruptionException: Thread.currentThread().interrupt()

Então, tentei brincar com a amostra de listagem para entender. Mas estou confuso com a saída.

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());
        }
    }
}

método principal##

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; 
    }

Resultado:

// 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

Pergunta

  1. Apenas adicionando TimeUnit.SECONDS.sleep(5) no thread principal da classe principal. O status de interrupção do thread em execução (ou seja, gerador) está sendo redefinido. Se eu comentar o método TimeUnit.SECONDS.sleep(5), nesse caso, o status de interrupção será retido. Por que isso está acontecendo e como?

  2. No livro, é mencionado que um thread deve ser interrompido apenas por seu proprietário. Aqui no exemplo acima, quem é o proprietário? Acho que é o thread do método principal.

Respostas

1 Joni Aug 26 2020 at 04:25

Ao adicionar TimeUnit.SECONDS.sleep(5), você está dando tempo suficiente para o encadeamento terminar.

Quando um thread termina, seu sinalizador de interrupção é limpo.

Isso não está documentado na especificação, mas é o que acontece. Veja, por exemplo , este relatório de bug :

Não há nenhuma especificação sendo violada aqui, então fiz uma solicitação de aprimoramento em vez de um bug. Indiscutivelmente, a falta de especificação é um bug - especificamos intencionalmente que "a interrupção após o término não precisa afetar" para lidar com o fato de que o estado da interrupção é armazenado na VM e não existe mais depois que um encadeamento é encerrado. No entanto, deixamos de refletir isso na especificação Thread.isInterrupted.

Sem o extra sleep, suspeito que, em teoria, você poderia ver ambos truee falseinterromper o status porque há uma condição de corrida, mas é muito mais provável que você veja truegraças ao agendamento de encadeamento. A janela de tempo em que o status da interrupção é falso, entre a exceção lançada e a restauração do status da interrupção no bloco catch, é incrivelmente pequena.

2 michalk Aug 26 2020 at 03:49

Apenas adicionando TimeUnit.SECONDS.sleep(5) no thread principal da classe principal. O status de interrupção do thread em execução (ou seja, gerador) está sendo redefinido. Se eu comentar o método TimeUnit.SECONDS.sleep(5), nesse caso, o status de interrupção será retido. Por que isso está acontecendo e como?

Você não está usando nenhum mecanismo de sincronização (além da fila de bloqueio) entre o thread principal e CorrectPrimeProducerquando o thread principal imprime o status - o CorrectPrimeProducerpode não ter preservado o status interrompido ainda (executando catchinstruções de bloco), portanto, você obtém falseo resultado.

Quando você adiciona sleepao principal Thread, você apenas aumenta a possibilidade de que o CorrectPrimeProducerthread preserve o status de interrupção invocando catchinstruções de bloco antes que o thread principal tente imprimir seu status. É por isso que imprime true.

No livro, é mencionado que um thread deve ser interrompido apenas por seu proprietário. Aqui no exemplo acima, quem é o proprietário? Acho que é o thread do método principal.

Nesse caso, você é o proprietário (o proprietário é o código que cria o encadeamento) do CorrectPrimeProducerencadeamento, portanto, você decide o que a interrupção significa para ele. Por exemplo, você pode recriá-lo se foi interrompido (isso acontece, por exemplo, para Threads de conjuntos de encadeamentos java por padrão).