Manipulando InterruptedException em Java

Qual é a diferença entre as seguintes maneiras de lidar com 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: Eu também gostaria de saber em quais cenários são esses dois usados.

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

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

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

Faz sentido que o método que você está implementando lance uma InterruptedException ? Em outras palavras, uma InterruptedException é um resultado sensato ao chamar seu método?

  • Se sim , então throws InterruptedException devem fazer parte da sua assinatura de método, e você deve deixar a exceção se propagar (ou seja, não pegar nada).

    Exemplo : seu método aguarda um valor da rede para concluir a computação e retornar um resultado. Se a chamada de rede de bloqueio lança uma InterruptedException seu método não pode concluir o cálculo de uma maneira normal. Você deixa o InterruptedException propagar.

     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 , então você não deve declarar seu método com throws InterruptedException e você deve (deve!) Pegar a exceção. Agora, duas coisas são importantes para se manter em mente nessa situação:

    1. Alguém interrompeu o seu tópico. Esse alguém provavelmente está ansioso para cancelar a operação, terminar o programa normalmente, ou o que for. Você deve ser educado com esse 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 uma InterruptedException o fato de o thread ter sido interrompido ainda pode ser importante. Em particular, o código que chama seu método pode estar interessado em saber se uma interrupção ocorreu durante a execução de seu método. Portanto, você deve registrar o fato de que uma interrupção ocorreu definindo o sinalizador interrompido: Thread.currentThread().interrupt()

    Exemplo : o usuário pediu para imprimir a sum de dois valores. A impressão ” Failed to compute sum ” é aceitável se a sum não puder ser calculada (e muito melhor do que permitir que o programa trave com um rastreamento de pilha devido a uma InterruptedException ). Em outras palavras, não faz sentido declarar este 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"); } } 

Por agora, deve ficar claro que apenas fazer o throw new RuntimeException(e) é uma má ideia. Não é muito educado para o chamador. Você poderia inventar uma nova exceção de tempo de execução, mas a causa raiz (alguém quer que o segmento pare a execução) pode se perder.

Outros exemplos:

Implementando o Runnable : Como você pode ter descoberto, a assinatura do Runnable.run não permite o relançamento do InterruptedExceptions . Bem, você se inscreveu na implementação do Runnable , o que significa que você se inscreveu para lidar com possíveis InterruptedExceptions . Escolha uma interface diferente, como Callable , ou siga a segunda abordagem acima.

 

Chamando Thread.sleep : Você está tentando ler um arquivo e a especificação diz que você deve tentar 10 vezes com 1 segundo entre eles. Você chama Thread.sleep(1000) . Então, você precisa lidar com InterruptedException . Para um método como tryToReadFile , faz todo o sentido dizer: “Se eu for interrompido, não consigo concluir minha ação de tentar ler o arquivo” . Em outras palavras, faz todo o 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; } 

Este post foi reescrito como um artigo aqui .

Acontece que eu estava lendo sobre isso esta manhã no meu caminho para trabalhar em Java concurrency na Prática de Brian Goetz. Basicamente ele diz que você deveria fazer uma de duas coisas

  1. Propagar o InterruptedException – Declare seu método para lançar o InterruptedException marcado para 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 o InterruptedException e restaurar o status de interrupção chamando o método interrupt() no currentThread para que o código mais alto da pilha de chamadas possa ver que uma interrupção foi emitida.

O que você está tentando fazer?

A InterruptedException é lançada quando um encadeamento está aguardando ou dormindo e outro encadeamento o interrompe usando o método de interrupt na class Thread . Então, se você pegar essa exceção, isso significa que o segmento foi interrompido. Geralmente não faz sentido chamar Thread.currentThread().interrupt(); novamente, a menos que você queira verificar o status “interrompido” do thread de algum outro lugar.

Em relação à sua outra opção de lançar uma RuntimeException , não parece ser uma coisa muito sensata a se fazer (quem vai perceber isso? Como ela será tratada?), Mas é difícil dizer mais sem informações adicionais.

Para mim, a principal coisa sobre isso é: um InterruptedException não é nada errado, é o segmento fazendo o que você disse para fazer. Portanto, relançá-lo em uma RuntimeException não faz sentido.

Em muitos casos, faz sentido relançar uma exceção envolvida em uma 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 stream de processamento atual e acertar qualquer manipulador de exceções em todo o aplicativo que eu tenha, para que possa registrá-lo. Esse não é o caso de uma InterruptedException, é apenas o encadeamento que responde a ter interrompido () chamado, está lançando o InterruptedException para ajudar a cancelar o processamento do encadeamento em tempo hábil.

Portanto, propague o InterruptedException, ou coma-o de forma inteligente (significando em um lugar onde ele terá realizado o que ele 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 os valores de exceção para manipulá-los, portanto, por padrão, o sinalizador é limpo.

Então, definitivamente, a primeira maneira é melhor, o segundo exemplo postado na questão não é útil, a menos que você não espere que a thread seja realmente interrompida, e a interrupção 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 está usando o InterruptedException para salvar um loop while no método de execução do Runnable.

A opção padrão correta é adicionar InterruptedException à sua lista de lançamentos. Uma interrupção indica que outro segmento deseja que o segmento finalizar. A razão para este pedido não é evidente e é totalmente contextual, por isso, se você não tem nenhum conhecimento adicional, você deve assumir que é apenas um desligamento amigável, e qualquer coisa que evita que o desligamento seja uma resposta não amigável.

Java não irá lançar aleatoriamente o InterruptedException, todos os conselhos não afetarão sua aplicação, mas eu me deparei com um caso em que o desenvolvedor seguindo a estratégia de “engolir” se tornou muito inconveniente. Uma equipe desenvolveu um grande conjunto de testes e usou Thread.Sleep muito. Agora começamos a executar os testes em nosso servidor de CI e, às vezes, devido a defeitos no código, ficamos presos em esperas permanentes. Para piorar a situação, ao tentar cancelar o trabalho de IC, ele nunca fechou porque o Thread.Interrupt que pretendia abortar o teste não abortou o trabalho. Nós tivemos que entrar na checkbox e manualmente matar os processos.

Então, para encurtar a história, se você simplesmente lançar o InterruptedException, você está combinando a intenção padrão que o seu tópico deve terminar. Se você não puder adicionar InterruptedException à sua lista de lançamentos, eu a colocaria em uma RuntimeException.

Há um argumento muito racional a ser feito que InterruptedException deve ser um RuntimeException em si, uma vez que isso encorajaria um melhor tratamento “padrão”. Não é uma exceção RuntimeException porque os projetistas mantiveram uma regra categórica de que uma RuntimeException deve representar um erro em seu código. Como uma InterruptedException não surge diretamente de um erro no seu código, não é. Mas a realidade é que frequentemente uma InterruptedException surge porque há um erro em seu código (isto é, loop infinito, dead-lock), e o Interrupt é o método de algum outro thread para lidar com esse erro.

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

Então, em resumo, suas escolhas para manipulação devem seguir esta lista:

  1. Por padrão, adicione aos lances.
  2. Se não for permitido adicionar lances, lance RuntimeException (e). (Melhor escolha de várias opções ruins)
  3. Somente quando você souber uma causa explícita da interrupção, identifique como desejado. Se a sua manipulação é local para o seu método, então redefinir interrompido por uma chamada para Thread.currentThread (). Interromper ().

Eu só queria adicionar uma última opção ao que a maioria das pessoas e artigos mencionam. Como mR_fr0g declarou, é importante manipular a interrupção corretamente por:

  • Propagando a InterruptException

  • Restaurar estado de interrupção no thread

Ou adicionalmente:

  • Manuseio personalizado de interrupção

Não há nada de errado em lidar com a interrupção de maneira personalizada, dependendo das circunstâncias. Como uma interrupção é um pedido de encerramento, ao contrário de um comando forçado, é perfeitamente válido concluir o trabalho adicional para permitir que o aplicativo manipule a solicitação normalmente. Por exemplo, se um Thread estiver em espera, aguardando um IO ou uma resposta de hardware, quando receber a interrupção, é perfeitamente válido fechar todas as conexões antes de encerrar o thread.

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

Eu diria que em alguns casos não há problema em fazer nada. Provavelmente não é algo que você deve fazer por padrão, mas no caso de não haver nenhuma maneira de a interrupção acontecer, não tenho certeza do que fazer (provavelmente registrando erro, mas isso não afeta o stream do programa).

Um caso seria no caso de você ter uma fila de tarefas (bloqueio). No caso de você ter um daemon encadeando essas tarefas e não interromper o encadeamento sozinho (de acordo com o meu conhecimento, o jvm não interrompe os encadeamentos do daemon no encerramento do jvm), não vejo como a interrupção acontecer e, portanto, poderia ser apenas ignorado. (Eu sei que um thread daemon pode ser morto pela jvm a qualquer momento e, portanto, são inadequados em alguns casos).

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