Pregunta

Un compañero de equipo hizo el siguiente reclamo:

"Thread.interrupt() está inherentemente roto y (casi) nunca debe usarse ".

Estoy tratando de entender por qué este es el caso.

¿Es una mejor práctica conocida nunca usar? Thread.interrupt()? ¿Puede proporcionar evidencia de por qué está roto / erróneo, y no debe usarse para escribir un código múltiple robusto?

Nota - No estoy interesado en esta pregunta si es "bonito" de un conservante de diseño. Mi pregunta es: ¿es un error?

¿Fue útil?

Solución

Version corta:

¿Es una mejor práctica conocida nunca usar Thread.interrupt ()?

No.

¿Puede proporcionar evidencia de por qué está roto / buggie, y no debe usarse para escribir un código múltiple robusto?

Lo contrario es cierto: es fundamental para el código multiproceso.

Ver Listado 7.7 en Concurrencia de Java en la práctica para un ejemplo.

Versión más larga:

Por aquí, usamos este método en un lugar específico: manejo InterruptedExceptions. Eso puede parecer un poco extraño, pero así es como se ve en el 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();
}

Esto hace dos cosas por nosotros:

  1. Evita comer la excepción de interrupción. Los manejadores de Auto-Exception IDE siempre le proporcionan algo como ie.printStackTrace(); Y un alegre "TODO: ¡algo útil debe ir aquí!" comentario.
  2. Restaura el estado de interrupción sin forzar una excepción verificada en este método. Si la firma del método que está implementando no tiene un throws InterruptedException Cláusula, esta es su otra opción para propagar ese estado interrumpido.

Un comentarista sugirió que debería usar una excepción sin control "para forzar el hilo a morir". Esto supone que tengo conocimiento previo de que matar el hilo abruptamente es lo correcto. Yo no.

Para citar a Brian Goetz de JCIP en la página antes del listado citado anteriormente:

Una tarea no debe asumir nada sobre la política de interrupción de su hilo de ejecución a menos que esté explícitamente diseñado para ejecutarse dentro de un servicio que tenga una política de interrupción específica.

Por ejemplo, imagina que hice esto:

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

Estaría declarando que, independientemente de otras obligaciones del resto de la pila de llamadas y el estado asociado, este hilo debe morir en este momento. Intentaría escabullirme de todos los otros bloques de captura y estado del código de limpieza para obtener directamente a la muerte del hilo. Peor aún, habría consumido el estado interrumpido del hilo. La lógica ascendente ahora tendría que deconstruir mi excepción para tratar de desconcertarse si había un error de lógica del programa o si estoy tratando de ocultar una excepción verificada dentro de un envoltorio oscuro.

Por ejemplo, esto es lo que todos los demás en el equipo tendrían que hacer de inmediato:

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

El estado interrumpido es más importante que cualquier específico InterruptException. Para obtener un ejemplo específico por qué, consulte el Javadoc para Thread.interrupt ():

Si este hilo se bloquea en una invocación de los métodos Wait (), Wait (Long) o Wait (Long, Int) de la clase de objeto, o de la unión (), unirse (Long), unirse (Long, int) , dormir (largo) o dormir (largo, int), métodos de esta clase, entonces su estado de interrupción se despejará y recibirá una Excepción InterruptedException.

Como puede ver, más de una Excepción InterruptedException podría crearse y manejarse a medida que se procesan las solicitudes de interrupción, pero solo si se conserva ese estado de interrupción.

Otros consejos

La única forma en que soy consciente en la que Thread.interrupt() está roto es que en realidad no hace lo que parece que podría hacerlo, en realidad solo puede interrumpir código que lo escucha.

Sin embargo, utilizado correctamente, me parece un buen mecanismo incorporado para la gestión de tareas y la cancelación.

yo recomiendo Concurrencia de Java en la práctica para leer más sobre el uso adecuado y seguro de él.

El principal problema con Thread.interrupt() Es que la mayoría de los programadores no saben sobre las trampas ocultas y las usan de manera incorrecta. Por ejemplo, cuando maneja la interrupción, hay métodos que claro la bandera (para que el estado se pierda).

Además, la llamada no siempre interrumpirá el hilo de inmediato. Por ejemplo, cuando cuelga en alguna rutina del sistema, no pasará nada. De hecho, si el hilo no verifica la bandera y nunca llama a un método Java que lanza InterruptException, entonces interrumpirlo no tendrá ningún efecto.

No, no es buggy. En realidad, es la base de cómo detiene los hilos en Java. Se utiliza en el marco del ejecutor desde java.util.concurrent: consulte la implementación de java.util.concurrent.FutureTask.Sync.innerCancel.

En cuanto al fracaso, nunca lo he visto fallar, y lo he usado ampliamente.

Una razón no mencionada es que la señal de interrupción se puede perder, lo que hace que la invocación del método thread.interrupt () no tenga sentido. Entonces, a menos que su código en el método thread.run () esté girando en un bucle de tiempo, el resultado de llamar a thread.interrupt () es incierto.

Me di cuenta de que cuando estaba en hilo ONE Yo ejecuto DriverManager.getConnection() Cuando no hay una conexión de base de datos disponible (por ejemplo, el servidor está inactivo, por lo tanto, finalmente esta línea lanza SQLException) y del hilo TWO Explícito llamo ONE.interrupt(), entonces ambos ONE.interrupted() y ONE.isInterrupted() devolver false incluso si se coloca como la primera línea en el catch{} bloquear dónde SQLException es manejado.

Por supuesto, en la solución este problema implementé el semáforo adicional, pero es bastante problemático, ya que es el primer problema en mis 15 años de desarrollo de Java.

Supongo que es por error en com.microsoft.sqlserver.jdbc.SQLServerDriver. E investigé más para confirmar que la llamada al native La función consume esta excepción en todos los casos, se ajusta a la suya, pero la preserva cuando se logra.

Tomo

PD: Encontré el problema análogo.

PPS, encerro un ejemplo muy corto de lo que estoy escribiendo anteriormente. La clase registrada se puede encontrar en sqljdbc42.jar. Encontré este error en las clases construidas en 2015-08-20 y luego actualicé a la versión más reciente disponible (de 2017-01-12) y el error todavía 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();
    }
}

Si pasa algo completamente incorrecto, como "foo", hacia DriverManager.getConnection(), obtendrá el mensaje "No se encuentra el controlador adecuado para Foo", y la segunda impresión seguirá siendo cierta como cabría esperar. Pero si pasa la cadena construida correctamente pero, por ejemplo, su servidor está inactivo o ha perdido su conexión neta (que generalmente puede ocurrir en el entorno de producción), verá el java.net Error de errores de tiempo de espera de socket y el hilo interrupted() El estado se pierde.

El problema no es que la implementación no sea errónea, sino que su hilo está en un estado desconocido cuando se interrumpe y esto puede conducir a un comportamiento impredecible.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top