Pergunta

Um companheiro de equipe fez a seguinte reivindicação:

"Thread.interrupt() é inerentemente quebrado e (quase) nunca deve ser usado ".

Estou tentando entender por que esse é o caso.

É uma prática recomendada conhecida nunca usar Thread.interrupt()? Você pode fornecer evidências por que está quebrado / buggy e não deve ser usado para escrever um código multithread robusto?

Observação - Não estou interessado nessa pergunta se for "bonito" de um conservante de design. Minha pergunta é - é buggy?

Foi útil?

Solução

Versão curta:

É uma prática recomendada conhecida nunca usar thread.Interrup ()?

Não.

Você pode fornecer evidências por que está quebrado / buggie e não deve ser usado para escrever um código multithread robusto?

O oposto é verdadeiro: é fundamental para o código multithread.

Veja a Listagem 7.7 em Concorrência de java na prática Por exemplo.

Versão mais longa:

Por aqui, usamos este método em um lugar específico: manuseio InterruptedExceptions. Isso pode parecer um pouco estranho, mas aqui está o que parece no código:

try {
    // Some code that might throw an InterruptedException.  
    // Using sleep as an example
    Thread.sleep(10000);
} catch (InterruptedException ie) {
    System.err.println("Interrupted in our long run.  Stopping.");
    Thread.currentThread().interrupt();
}

Isso faz duas coisas para nós:

  1. Evita comer a exceção de interrupção. Os manipuladores de exceção automática ideais sempre fornecem algo como ie.printStackTrace(); E um alegre "TODO: algo útil precisa ir aqui!" Comente.
  2. Ele restaura o status de interrupção sem forçar uma exceção verificada neste método. Se a assinatura do método que você está implementando não tiver um throws InterruptedException Cláusula, esta é a sua outra opção para propagar esse status interrompido.

Um comentarista sugeriu que eu estivesse usando uma exceção desmarcada "para forçar o fio a morrer". Isso assume que tenho conhecimento prévio de que matar abruptamente o fio é a coisa adequada a se fazer. Eu não.

Para citar Brian Goetz do JCIP na página antes da listagem citada acima:

Uma tarefa não deve assumir nada sobre a política de interrupção de seu thread de execução, a menos que seja explicitamente projetado para ser executado em um serviço que tenha uma política de interrupção específica.

Por exemplo, imagine que eu fiz isso:

} catch (InterruptedException ie) {
    System.err.println("Interrupted in our long run.  Stopping.");
    // The following is very rude.
    throw new RuntimeException("I think the thread should die immediately", ie);
}

Eu declararia que, independentemente de outras obrigações do restante da pilha de chamadas e do estado associado, esse tópico precisa morrer agora. Eu estaria tentando passar por todos os outros blocos de captura e código de limpeza de estado para ir direto para a morte de Thread. Pior, eu teria consumido o status interrompido do tópico. A lógica upstream agora teria que desconstruir minha exceção para tentar confundir se houve um erro lógico do programa ou se estou tentando ocultar uma exceção verificada dentro de um invólucro obscurecido.

Por exemplo, eis o que todos os outros da equipe teriam que fazer imediatamente:

try {
    callBobsCode();
} catch (RuntimeException e) { // Because Bob is a jerk
    if (e.getCause() instanceOf InterruptedException) {
        // Man, what is that guy's problem?
        interruptCleanlyAndPreserveState();
        // Restoring the interrupt status
        Thread.currentThread().interrupt();
    }
}

O estado interrompido é mais importante do que qualquer específico InterruptException. Para um exemplo específico, por que, consulte o javadoc para Thread.Interrupt ():

Se este tópico estiver bloqueado em uma invocação dos métodos Wait (), Wait (Long) ou Wait (Long, int) , sono (longo), ou sono (longo, int), métodos dessa classe, então seu status de interrupção será limpo e receberá uma interrupção.

Como você pode ver, mais de uma interrupção pode ser criada e manipulada à medida que as solicitações de interrupção são processadas, mas apenas se esse status de interrupção for preservado.

Outras dicas

A única maneira de saber de que Thread.interrupt() está quebrado é que ele realmente não faz o que parece - só pode interromper Code que ouve isso.

No entanto, usado corretamente, parece-me um bom mecanismo interno para gerenciamento e cancelamento de tarefas.

Eu recomendo Concorrência de java na prática Para mais leitura sobre o uso adequado e seguro dele.

O principal problema com Thread.interrupt() É que a maioria dos programadores não conhece as armadilhas ocultas e as usa da maneira errada. Por exemplo, quando você lida com a interrupção, existem métodos que Claro a bandeira (para que o status se perca).

Além disso, a chamada nem sempre interrompe o tópico imediatamente. Por exemplo, quando fica em alguma rotina do sistema, nada acontecerá. De fato, se o tópico não verificar a bandeira e nunca chamar um método java que lança InterruptException, então interromper isso não terá nenhum efeito.

Não, não é buggy. Na verdade, é a base de como você interrompe os threads em Java. É usado na estrutura do executor de java.util.concurrent - veja a implementação de java.util.concurrent.FutureTask.Sync.innerCancel.

Quanto ao fracasso, nunca o vi falhar e usei extensivamente.

Um motivo não mencionado é que o sinal de interrupção pode ser perdido, o que faz com que a invocação do thread.Interrup () método sem sentido. Portanto, a menos que seu código no método Thread.run () esteja girando em um tempo, o resultado do encadeamento de chamadas.Interrup () é incerto.

Eu notei isso quando estiver em tópico ONE Eu executo DriverManager.getConnection() Quando não há uma conexão de banco de dados disponível (digamos que o servidor está desativado, finalmente, esta linha joga SQLException) e do tópico TWO Eu chamo explicitamente ONE.interrupt(), então ambos ONE.interrupted() e ONE.isInterrupted() Retorna false mesmo se colocado como a primeira linha no catch{} Bloqueie onde SQLException É tratado.

É claro que trabalhei com essa questão implementando o semáforo extra, mas é bastante problemático, pois é o primeiro problema desse tipo no meu desenvolvimento de 15 anos de Java.

Suponho que é por causa do bug em com.microsoft.sqlserver.jdbc.SQLServerDriver. E eu investigei mais para confirmar que a chamada para o native A função consome essa exceção em todos os casos, traz sua própria, mas a preserva quando sucedida.

Tomek

PS eu encontrei o questão análoga.

PPS I colo um exemplo muito curto do que estou escrevendo acima. A classe registrada pode ser encontrada em sqljdbc42.jar. Encontrei esse bug nas aulas construídas em 2015-08-20, então atualizei para a versão mais recente disponível (de 2017-01-12) e o bug ainda existe.

import java.sql.*;

public class TEST implements Runnable{
    static{
        try{
//register the proper driver
           Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        }
        catch(ClassNotFoundException e){
            System.err.println("Cannot load JDBC driver (not found in CLASSPATH).");
        }
    }

    public void run() {
        Thread.currentThread().interrupt();
        System.out.println(Thread.currentThread().isInterrupted());
//prints true
        try{
            Connection conn = DriverManager.getConnection("jdbc:sqlserver://xxxx\\xxxx;databaseName=xxxx;integratedSecurity=true");
        }
        catch (SQLException e){
            System.out.println(e.getMessage());
        }
        System.out.println(Thread.currentThread().isInterrupted());
//prints false
        System.exit(0);
    }

    public static void main(String[] args){
        (new Thread(new TEST())).start();
    }
}

Se você passar algo completamente incorreto, como "foo", para o DriverManager.getConnection(), você obterá a mensagem "Nenhum driver adequado encontrado para Foo", e a segunda impressão ainda será verdadeira como seria de esperar. Mas se você passar a string correta, mas, digamos, seu servidor está inativo ou você perde sua conexão de rede (que geralmente pode ocorrer no ambiente de produção), você verá o java.net Impressão de erro de tempo limite do soquete e o thread's interrupted() Estado está perdido.

O problema não é que a implementação não seja buggy, mas seu tópico está em um estado desconhecido quando é interrompido e isso pode levar a um comportamento imprevisível.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top