Historicamente, Java tentou fornecer interrupções limitadas preventivas, mas houve muitos problemas, como Thread.stop, Thread.suspend e Thread.resume abandonados introduzidos anteriormente. Por outro lado, considerando a robustez do código da aplicação Java, o limite de programação é reduzido e a probabilidade de programadores que não conhecem o mecanismo subjacente danificarem involuntariamente o sistema é reduzida.
Hoje, o escalonamento de threads Java não fornece interrupções preemptivas, mas usa interrupções cooperativas. Na verdade, o princípio da interrupção cooperativa é muito simples, que consiste em pesquisar uma determinada marca indicando uma interrupção. Podemos implementá-lo em qualquer código comum.
Por exemplo o seguinte código:
volátil bool é interrompido;
//…
while(!isInterrompido) {
calcular();
}
No entanto, os problemas de código acima também são óbvios. Quando o tempo de execução do cálculo é relativamente longo, a interrupção não pode ser respondida a tempo. Por outro lado, usando polling para verificar variáveis de sinalizadores, não há como interromper operações de bloqueio de threads, como espera e suspensão.
Se você ainda usa a ideia acima, se deseja que a interrupção seja respondida em tempo hábil, você deve verificar a variável mark por meio do agendamento de threads na parte inferior da máquina virtual. Sim, isso é realmente feito na JVM.
O seguinte foi extraído do código-fonte de java.lang.Thread:
public static boolean interrompido() {
retornar currentThread().isInterrupted(true);
}
//…
booleano nativo privado isInterrupted(boolean ClearInterrupted);
Pode-se descobrir que isInterrupted é declarado como um método nativo, que depende da implementação subjacente da JVM.
Na verdade, a JVM mantém um sinalizador de interrupção internamente para cada thread. Porém, a aplicação não pode acessar diretamente esta variável de interrupção e deve operar através dos seguintes métodos:
classe pública Tópico {
//Definir marca de interrupção
interrupção pública void() { ... }
//Obtém o valor da marca de interrupção
public boolean isInterrupted() { ... }
//Limpa a marca de interrupção e retorna o valor da última marca de interrupção
público estático booleano interrompido() { ... }
}
Normalmente, chamar o método de interrupção de um thread não causa uma interrupção imediatamente, mas apenas define o sinalizador de interrupção dentro da JVM. Portanto, ao verificar o sinalizador de interrupção, o aplicativo pode fazer algo especial ou ignorar completamente a interrupção.
Você pode pensar que se a JVM fornecer apenas esse mecanismo de interrupção bruto, ela basicamente não terá nenhuma vantagem em comparação com o método do próprio aplicativo para definir variáveis de interrupção e sondagem.
A principal vantagem das variáveis de interrupção internas da JVM é que elas fornecem um mecanismo para simular "armadilhas de interrupção" automáticas para determinadas situações.
Ao executar chamadas de bloqueio envolvendo agendamento de thread (como esperar, dormir e ingressar), se ocorrer uma interrupção, o thread bloqueado lançará InterruptedException "o mais rápido possível". Portanto, podemos usar a seguinte estrutura de código para lidar com interrupções de bloqueio de thread:
tentar {
//espera, dorme ou entra
}
catch(InterruptedExceptione) {
//Algum trabalho de tratamento de interrupções
}
Por "o mais rápido possível", acho que a JVM verifica a variável de interrupção no intervalo entre o agendamento do thread. A velocidade depende da implementação da JVM e do desempenho do hardware.
No entanto, para determinadas operações de bloqueio de encadeamento, a JVM não lança InterruptedException automaticamente. Por exemplo, certas operações de E/S e operações de bloqueio interno. Para este tipo de operação, as interrupções podem ser simuladas de outras formas:
1) E/S de soquete assíncrono em java.io
Ao ler e escrever soquetes, os métodos de leitura e gravação de InputStream e OutputStream serão bloqueados e aguardarão, mas não responderão às interrupções Java. No entanto, após chamar o método close do Socket, o thread bloqueado lançará uma SocketException.
2) E/S assíncrona implementada usando Seletor
Se o thread estiver bloqueado em Selector.select (em java.nio.channels), chamar o método wakeup causará uma exceção ClosedSelectorException.
3) Bloquear aquisição
Se uma thread estiver aguardando para adquirir um bloqueio interno, não poderemos interrompê-la. No entanto, usando o método lockInterruptably da classe Lock, podemos fornecer recursos de interrupção enquanto aguardamos o bloqueio.
Além disso, em uma estrutura onde tarefas e threads são separados, uma tarefa geralmente não sabe por qual thread será chamada e, portanto, não conhece a estratégia do thread chamador para lidar com interrupções. Portanto, após a tarefa definir o sinalizador de interrupção do thread, não há garantia de que a tarefa será cancelada. Portanto, existem dois princípios de programação:
1) Você não deve interromper um thread a menos que conheça sua política de interrupção.
Este princípio nos diz que não devemos chamar diretamente o método de interrupção de um thread em uma estrutura como Executer, mas devemos usar métodos como Future.cancel para cancelar tarefas.
2) O código da tarefa não deve adivinhar o que a interrupção significa para o thread de execução.
Este princípio nos diz que quando o código geral encontra uma InterruptedException, ele não deve capturá-la e "engoli-la", mas deve continuar a lançá-la para o código superior.
Em suma, o mecanismo de interrupção não preemptiva em Java exige que mudemos a ideia tradicional de interrupção preemptiva e adotemos princípios e padrões correspondentes para programação com base na compreensão de sua essência.