Manipulando InterruptedException em Java

317

Qual é a diferença entre as seguintes formas de manuseio InterruptedException? Qual é a melhor maneira de fazer isso?

try{
 //...
} catch(InterruptedException e) { 
   Thread.currentThread().interrupt(); 
}

OU

try{
 //...
} catch(InterruptedException e) {
   throw new RuntimeException(e);
}

EDIT: Gostaria de saber também em quais cenários esses dois são usados.

devnull
fonte

Respostas:

436

Qual é a diferença entre as seguintes maneiras de lidar com o InterruptedException? Qual é a melhor maneira de fazer isso?

Você provavelmente veio fazer essa pergunta porque chamou um método que lança InterruptedException.

Primeiro de tudo, você deve ver throws InterruptedExceptiono que é: Uma parte da assinatura do método e um possível resultado de chamar o método que você está chamando. Portanto, comece abraçando o fato de que um InterruptedExceptioné um resultado perfeitamente válido da chamada do método.

Agora, se o método que você está chamando gera essa exceção, o que seu método deve fazer? Você pode descobrir a resposta pensando no seguinte:

Faz sentido para o método que você está implementando lançar um InterruptedException? Em outras palavras, é um InterruptedExceptionresultado sensato ao chamar seu método?

  • Se sim , então throws InterruptedExceptiondeve ser parte de sua assinatura do método, e você deve deixar a propagar exceção (ou seja, não pegá-lo em tudo).

    Exemplo : Seu método aguarda um valor da rede para concluir o cálculo e retornar um resultado. Se a chamada de rede bloqueada for lançada, InterruptedExceptionseu método não poderá concluir o cálculo normalmente. Você deixa a InterruptedExceptionpropagação.

    int computeSum(Server server) throws InterruptedException {
        // Any InterruptedException thrown below is propagated
        int a = server.getValueA();
        int b = server.getValueB();
        return a + b;
    }
  • Se não , você não deve declarar seu método com throws InterruptedExceptione deve (deve!) Capturar a exceção. Agora, duas coisas são importantes a serem lembradas nessa situação:

    1. Alguém interrompeu seu tópico. É provável que alguém esteja ansioso para cancelar a operação, encerrar o programa normalmente ou qualquer outra coisa. Você deve ser educado com alguém e retornar do seu método sem mais delongas.

    2. Mesmo que seu método consiga produzir um valor de retorno sensato no caso de InterruptedExceptiono fato de o encadeamento ter sido interrompido, ainda pode ser importante. Em particular, o código que chama seu método pode estar interessado em saber se ocorreu uma interrupção durante a execução do seu método. Portanto, você deve registrar o fato de uma interrupção configurando o sinalizador interrompido:Thread.currentThread().interrupt()

    Exemplo : O usuário pediu para imprimir uma soma de dois valores. A impressão " Failed to compute sum" é aceitável se a soma não puder ser calculada (e muito melhor do que deixar o programa travar com um rastreamento de pilha devido a um InterruptedException). Em outras palavras, não faz sentido declarar esse método com throws InterruptedException.

    void printSum(Server server) {
         try {
             int sum = computeSum(server);
             System.out.println("Sum: " + sum);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();  // set interrupt flag
             System.out.println("Failed to compute sum");
         }
    }

Agora, deve ficar claro que apenas fazer throw new RuntimeException(e)é uma má ideia. Não é muito educado para quem liga. Você pode inventar uma nova exceção de tempo de execução, mas a causa raiz (alguém deseja que o encadeamento interrompa a execução) pode se perder.

Outros exemplos:

ImplementandoRunnable : Como você deve ter descoberto, a assinatura de Runnable.runnão permite o relançamento InterruptedExceptions. Bem, você se inscreveu na implementação Runnable, o que significa que se inscreveu para lidar com o possível InterruptedExceptions. Escolha uma interface diferente, como Callable, ou siga a segunda abordagem acima.

 

LigandoThread.sleep : você está tentando ler um arquivo e a especificação diz que você deve tentar 10 vezes com 1 segundo no meio. Você liga Thread.sleep(1000). Então, você precisa lidar com isso InterruptedException. Para um método como o tryToReadFileque faz todo o sentido dizer: "Se eu for interrompido, não posso concluir minha ação de tentar ler o arquivo" . Em outras palavras, faz todo sentido para o método lançar InterruptedExceptions.

String tryToReadFile(File f) throws InterruptedException {
    for (int i = 0; i < 10; i++) {
        if (f.exists())
            return readFile(f);
        Thread.sleep(1000);
    }
    return null;
}

Esta postagem foi reescrita como um artigo aqui .

aioobe
fonte
Qual é o problema com o segundo método?
Blitzkriegz
4
O artigo diz que você deve ligar interrupt()para preservar o status interrompido. Qual é a razão por trás de não fazer isso em um Thread.sleep()?
oksayt
7
Eu não concordo, no caso em que seu encadeamento não suporta interrupções, qualquer interrupção recebida indica uma condição inesperada (ou seja, um erro de programação, mau uso da API), e o resgate com um RuntimeException é uma resposta apropriada (falha rápida ) A menos que seja considerado parte do contrato geral de um encadeamento, mesmo que você não suporte interrupções, você deve continuar a operação ao recebê-las.
amoe
12
Chamar métodos que lançam InterruptedExceptions e dizem que você "não oferece suporte a interrupções" parece uma programação desleixada e um bug esperando para acontecer. Colocar de forma diferente; Se você chamar um método declarado para lançar InterruptedException, não deverá ser uma condição inesperada se esse método realmente lançar essa exceção.
aioobe 7/09/15
1
@ MartiNito, não, chamar Thread.currentThread.interrupt()um InterruptedExceptionbloco catch apenas definirá o sinalizador de interrupção. Não fará com que outro InterruptedExceptionseja jogado / preso em um InterruptedExceptionbloco de captura externo .
aioobe 13/05/19
97

Por acaso, eu estava lendo sobre isso hoje de manhã no meu caminho para trabalhar em Java Concurrency In Practice por Brian Goetz. Basicamente, ele diz que você deve fazer uma de três coisas

  1. Propague oInterruptedException - Declare seu método para jogar o marcado, InterruptedExceptionpara que o chamador tenha que lidar com ele.

  2. Restaurar a interrupção - às vezes você não pode jogar InterruptedException. Nesses casos, você deve capturar InterruptedExceptione restaurar o status da interrupção chamando o interrupt()método no, currentThreadpara que o código mais alto na pilha de chamadas possa ver que uma interrupção foi emitida e retornar rapidamente do método. Nota: isso só é aplicável quando o seu método possui semântica "try" ou "best esforço", ou seja, nada crítico aconteceria se o método não atingir seu objetivo. Por exemplo, log()ou sendMetric()pode ser esse método, ou boolean tryTransferMoney(), mas não void transferMoney(). Veja aqui para mais detalhes.

  3. Ignore a interrupção no método, mas restaure o status na saída - por exemplo, via Guava's Uninterruptibles. Uninterruptiblesassuma o código padrão, como no exemplo Tarefa não cancelável no JCIP § 7.1.3.
mR_fr0g
fonte
10
"Às vezes você não pode lançar InterruptedException" - eu diria que, às vezes, não é apropriado que o método propagaet InterruptedExceptions. Sua formulação parece sugerir que você deve repetir novamente a InterruptedException sempre que puder .
aioobe
1
E se você leu o capítulo 7, verá que existem algumas sutilezas, como as da Listagem 7.7, em que uma tarefa não cancelável não restaura a interrupção imediatamente , mas somente depois que ela é concluída. Goetz também tem muitos co-autores para esse livro ...
Fizz
1
Gosto da sua resposta porque é concisa; no entanto, acredito que também deve declarar que você deve retornar rápida e gentilmente de sua função no segundo caso. Imagine um loop longo (ou infinito) com um Thread.sleep () no meio. A captura da InterruptedException deve chamar Thread.currentThread (). Interrupt () e interromper o loop.
Yann Vo
@YannVo Editei a resposta para aplicar sua sugestão.
leventov
18

O que você está tentando fazer?

A InterruptedExceptioné acionada quando um segmento está aguardando ou dormir e outro interrupções de rosca-lo usando o interruptmétodo em sala de aula Thread. Portanto, se você capturar essa exceção, significa que o encadeamento foi interrompido. Normalmente, não há motivo para ligar Thread.currentThread().interrupt();novamente, a menos que você queira verificar o status "interrompido" do encadeamento de outro lugar.

Em relação à sua outra opção de jogar a RuntimeException, não parece uma coisa muito sábia a ser feita (quem entenderá isso? Como será tratado?), Mas é difícil dizer mais sem informações adicionais.

Grodriguez
fonte
14
A chamada Thread.currentThread().interrupt()define o sinalizador interrompido (novamente), o que é realmente útil se queremos garantir que a interrupção seja notada e processada em um nível superior.
Péter Török
1
@ Péter: Eu estava apenas atualizando a resposta para mencionar isso. Obrigado.
Grodriguez 20/10/10
12

Para mim, a principal coisa sobre isso é: uma exceção InterruptedException não está dando errado, é o thread que está fazendo o que você pediu. Portanto, reescrevê-lo em um RuntimeException não faz sentido.

Em muitos casos, faz sentido retroceder uma exceção agrupada em um RuntimeException quando você diz: não sei o que deu errado aqui e não posso fazer nada para corrigi-lo, só quero que ele saia do fluxo de processamento atual e clique em qualquer manipulador de exceções em todo o aplicativo que possua para que possa registrá-lo. Esse não é o caso de uma InterruptedException, é apenas o thread que responde a uma interrupção (), está lançando o InterruptedException para ajudar a cancelar o processamento do thread em tempo hábil.

Portanto, propague a InterruptedException ou coma-a de maneira inteligente (ou seja, em um local em que ela cumpra o que deveria fazer) e redefina o sinalizador de interrupção. Observe que o sinalizador de interrupção é limpo quando a InterruptedException é lançada; a suposição que os desenvolvedores da biblioteca Jdk fazem é que capturar a exceção equivale a manipulá-la, portanto, por padrão, o sinalizador é limpo.

Então, definitivamente, a primeira maneira é melhor, o segundo exemplo postado na pergunta não é útil, a menos que você não espere que o encadeamento seja realmente interrompido, e interrompê-lo equivale a um erro.

Aqui está uma resposta que escrevi descrevendo como as interrupções funcionam, com um exemplo . Você pode ver no código de exemplo onde ele está usando a InterruptedException para salvar um loop while no método runnable.

Nathan Hughes
fonte
10

A opção padrão correta é adicionar InterruptedException à sua lista de lançamentos. Uma interrupção indica que outro segmento deseja que seu segmento termine. A razão para esta solicitação não é evidenciada e é totalmente contextual; portanto, se você não tiver nenhum conhecimento adicional, deve presumir que é apenas um desligamento amigável e qualquer coisa que evite esse desligamento é uma resposta não amigável.

O Java não lançará aleatoriamente o InterruptedException, todos os conselhos não afetarão seu aplicativo, mas encontrei um caso em que o desenvolvedor que seguiu a estratégia de "engolir" se tornou muito inconveniente. Uma equipe desenvolveu um grande conjunto de testes e usou muito o Thread.Sleep. Agora começamos a executar os testes em nosso servidor de IC e, às vezes, devido a defeitos no código, ficavam presos em esperas permanentes. Para piorar a situação, ao tentar cancelar o trabalho de IC, ele nunca foi fechado porque o Thread.Interrupt destinado a interromper o teste não interrompeu o trabalho. Tivemos que entrar na caixa e matar manualmente os processos.

Para encurtar a história, se você simplesmente lançar a InterruptedException, estará correspondendo à intenção padrão de que seu encadeamento termine. Se você não pode adicionar InterruptedException à sua lista de lançamentos, eu a agruparia em uma RuntimeException.

Há um argumento muito racional a ser feito de que InterruptedException deve ser uma RuntimeException em si, pois isso incentivaria uma melhor manipulação "padrão". Não é uma RuntimeException apenas porque os designers aderiram a uma regra categórica de que uma RuntimeException deve representar um erro no seu código. Como uma InterruptedException não surge diretamente de um erro no seu código, não é. Mas a realidade é que muitas vezes surge uma InterruptedException porque há um erro no seu código (ou seja, loop infinito, dead-lock), e a Interrupção é outro método de outro encadeamento para lidar com esse erro.

Se você sabe que há uma limpeza racional a ser feita, faça-o. Se você conhece uma causa mais profunda da interrupção, pode assumir um tratamento mais abrangente.

Portanto, em resumo, suas opções de manuseio devem seguir esta lista:

  1. Por padrão, adicione às jogadas.
  2. Se não for permitido adicionar jogadas, jogue RuntimeException (e). (Melhor escolha de várias opções ruins)
  3. Somente quando você souber uma causa explícita da interrupção, manipule conforme desejado. Se o seu tratamento for local para o seu método, redefina a interrupção por uma chamada para Thread.currentThread (). Interrupt ().
nedruod
fonte
3

Eu só queria adicionar uma última opção ao que a maioria das pessoas e artigos menciona. Como o mR_fr0g afirmou, é importante lidar com a interrupção corretamente:

  • Propagando a InterruptException

  • Restaurar estado de interrupção no encadeamento

Ou adicionalmente:

  • Manuseio personalizado de interrupção

Não há nada errado em lidar com a interrupção de maneira personalizada, dependendo das circunstâncias. Como uma interrupção é uma solicitação de encerramento, em oposição a um comando poderoso, é perfeitamente válido concluir trabalhos adicionais para permitir que o aplicativo manipule a solicitação normalmente. Por exemplo, se um encadeamento estiver inativo, aguardando resposta de E / S ou de hardware, quando receber a interrupção, será perfeitamente válido fechar normalmente todas as conexões antes de encerrar o encadeamento.

Eu recomendo entender o tópico, mas este artigo é uma boa fonte de informações: http://www.ibm.com/developerworks/java/library/j-jtp05236/

A coisa
fonte
0

Eu diria que, em alguns casos, não há problema em não fazer nada. Provavelmente não é algo que você deva fazer por padrão, mas, caso não ocorra uma interrupção, não sei mais o que fazer (provavelmente erro de log, mas isso não afeta o fluxo do programa).

Um caso seria o caso de você ter uma fila de tarefas (bloqueio). Caso você tenha um Thread daemon lidando com essas tarefas e não interrompa o Thread sozinho (que eu saiba, a jvm não interrompe os threads daemon no desligamento da jvm), não vejo como a interrupção ocorra e, portanto, poderia ser apenas ignorado. (Eu sei que um encadeamento daemon pode ser morto pela jvm a qualquer momento e, portanto, é inadequado em alguns casos).

EDIT: Outro caso pode ser um bloco protegido, pelo menos com base no tutorial da Oracle em: http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Bjarne Boström
fonte
Este é um mau conselho. Tenho um problema extremo ao pensar em qualquer tarefa que seja tão importante que ignore interrupções. O Oracle provavelmente era preguiçoso e não queria adicionar (mais) confusão à invocação de Thread.sleep (). A menos que você saiba por que seu encadeamento está sendo interrompido, pare o que estiver fazendo (retorne ou repita uma exceção para ajudar seu encadeamento a morrer o mais rápido possível).
Ajax
Por isso eu disse algumas vezes. Também porque ainda não foi sugerido e, em alguns casos , é perfeitamente adequado em minha opinião. Eu acho que depende do tipo de software que você está construindo, mas se você tiver um thread de trabalho 24 horas por dia, 7 dias por semana, que nunca deve parar (além de desligar a JVM), trataria interrupções, por exemplo, pegar um item de um BlockingQueue como um erro (ou seja, log), pois "isso não deveria acontecer" e tente novamente. Por cortesia, eu teria sinalizadores separados para verificar o encerramento do programa. Em alguns dos meus programas, o desligamento da JVM não é algo que deve ocorrer em operação normal.
Bjarne Boström
Ou, de outra forma: Na minha opinião, é melhor arriscar ter instruções extras de log de erros (e, por exemplo, enviar e-mails nos logs de erros) do que ter um aplicativo que deve estar em execução 24 horas por dia, sete dias por semana para parar devido a uma interrupção, para a qual Não vejo como acontecer durante condições normais.
Bjarne Boström
Bem, seus casos de uso podem ser diferentes, mas em um mundo ideal, você não termina apenas porque foi interrompido. Você para de tirar o trabalho da fila e deixa esse segmento morrer. Se o planejador de encadeamentos também for interrompido e instruído a encerrar, a JVM será encerrada e o jogo terminará. Em particular, se você enviar kill -9 ou estiver tentando derrubar o servidor de email ou o que quer que seja, ele o ignorará até você forçar o encerramento, impedindo, assim, que outras partes do seu código sejam desligadas normalmente. Forçar a morte enquanto escreve no disco ou no db, e tenho certeza de que coisas maravilhosas acontecerão.
Ajax