Jugando con la concurrencia: ¿cómo detener los hilos con elegancia?

Inserte la descripción de la imagen aquí

Utilice el método de parada

Llamar al método de detención terminará directamente el subproceso en ejecución, lo que puede evitar que se complete algún trabajo de limpieza. Y detener se ha marcado como un método obsoleto y no se recomienda su uso.

La postura correcta es utilizar un modo de terminación de dos etapas, es decir, un subproceso envía una instrucción de terminación y el otro subproceso recibe la instrucción y decide cuándo detenerse.

Usar bandera

public class RunTask {
    
    

    private volatile boolean stopFlag;
    private Thread taskThread;

    public void start() {
    
    
        taskThread = new Thread(() -> {
    
    
            while (!stopFlag) {
    
    
                System.out.println("doSomething");
            }
        });
        taskThread.start();
    }

    public void stop() {
    
    
        stopFlag = true;
    }
}

Agregar volátil a stopFlag es garantizar la visibilidad. Mi ejemplo usa un bucle while para juzgar continuamente. Si while no se usa en el proyecto, puede juzgar en el nodo clave y luego salir del método de ejecución.

Usa el método de interrupción

Si hay una lógica de bloqueo en nuestra tarea, como llamar al método Thread.sleep, ¿cómo detener el hilo?

Buscando la respuesta del diagrama de transición del estado del hilo
Inserte la descripción de la imagen aquí
En el diagrama, puede ver que si desea que un hilo entre en el estado de terminación, la premisa es que el hilo está en el estado de ejecución. Cuando queremos terminar un hilo, si el hilo está en un estado de bloqueo en este momento, ¿cómo lo cambiamos al estado de ejecución?

Podemos transferir el hilo bloqueado al estado listo llamando al método de interrupción del hilo #, entrar en el estado de ejecución programado por el sistema operativo y luego finalizar.

¿Qué sucede cuando el hilo llama al método de interrupción en el estado de ejecución?

public class RunTaskCase1 {
    
    

    private Thread taskThread;

    public void start() {
    
    
        taskThread = new Thread(() -> {
    
    
            while (true) {
    
    
                System.out.println("doSomething");
            }
        });
        taskThread.start();
    }

    public void stop() {
    
    
        taskThread.interrupt();
    }
}

Llame al método de inicio y al método de detención a su vez, y descubrió que el hilo no se detuvo.

De hecho, cuando el hilo se está ejecutando, el método de interrupción solo marca una parada en el hilo actual, y la lógica de la parada debe ser implementada por nosotros mismos.

La clase Thread proporciona los dos métodos siguientes para determinar si el hilo se interrumpe

  1. isInterrupted
  2. interrumpido

Aunque estos dos métodos pueden juzgar el estado, existen diferencias sutiles.

 @Test
 public void testInterrupt() throws InterruptedException {
    
    
     Thread thread = new Thread(() -> {
    
    
         while (true) {
    
    }
     });
     thread.start();
     TimeUnit.MICROSECONDS.sleep(100);
     thread.interrupt();
     // true
     System.out.println(thread.isInterrupted());
     // true
     System.out.println(thread.isInterrupted());
     // true
     System.out.println(thread.isInterrupted());
 }
 @Test
 public void testInterrupt2() {
    
    
     Thread.currentThread().interrupt();
     // true
     System.out.println(Thread.interrupted());
     // false
     System.out.println(Thread.interrupted());
     // false
     System.out.println(Thread.interrupted());
 }

La diferencia entre isInterrupted e interrumpido es la siguiente

Thread # isInterrupted: prueba si el hilo está interrumpido y el indicador de estado no se cambiará después de la ejecución.
Thread # interrumpido: prueba si el hilo está interrumpido y cambia el indicador de interrupción a falso después de la ejecución.

Entonces, en este momento no necesitamos definir nuestro propio estado, solo use la bandera de interrupción directamente, el código anterior se puede cambiar de la siguiente manera

public class RunTaskCase2 {
    
    

    private Thread taskThread;

    public void start() {
    
    
        taskThread = new Thread(() -> {
    
    
            while (!Thread.currentThread().isInterrupted()) {
    
    
                System.out.println("doSomething");
            }
        });
        taskThread.start();
    }

    public void stop() {
    
    
        taskThread.interrupt();
    }
}

Cuando el hilo está en un estado bloqueado, llamar al método de interrupción arrojará una InterruptedException y también terminará la ejecución del hilo.

Nota: Cuando ocurre una excepción, el indicador de interrupción del hilo cambiará de verdadero a falso.

Entonces tenemos la siguiente implementación.
Cuando el hilo está en el estado de ejecución: salir con un bit de bandera autodefinido.
Cuando el hilo está en el estado de bloqueo: salir lanzando una excepción

public class RunTaskCase3 {
    
    

    private volatile boolean stopFlag;
    private Thread taskThread;

    public void start() {
    
    
        taskThread = new Thread(() -> {
    
    
            while (stopFlag) {
    
    
                try {
    
    
                    System.out.println("doSomething");
                    TimeUnit.MICROSECONDS.sleep(100);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        });
        taskThread.start();
    }

    public void stop() {
    
    
        stopFlag = true;
        taskThread.interrupt();
    }
}

Por supuesto, siempre puede usar el indicador de interrupción para salir Tenga en cuenta que el bit del indicador de interrupción debe restablecerse cuando ocurre una excepción .

public class RunTaskCase4 {
    
    

    private Thread taskThread;

    public void start() {
    
    
        taskThread = new Thread(() -> {
    
    
            while (!Thread.currentThread().isInterrupted()) {
    
    
                try {
    
    
                    System.out.println("doSomething");
                    TimeUnit.MICROSECONDS.sleep(100);
                } catch (InterruptedException e) {
    
    
                    // 重置中断标志位为true
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
            }
        });
        taskThread.start();
    }

    public void stop() {
    
    
        taskThread.interrupt();
    }
}

¿Una última pregunta para todos? ¿Qué método de implementación es mejor para RunTaskCase3 y RunTaskCase4?

Aunque el código de RunTaskCase4 parece más conciso, no se recomienda utilizar RunTaskCase4, porque si se llama a una biblioteca de terceros en el método de ejecución, se produce una InterruptedException, pero el bit del indicador de interrupción no se restablece, lo que hará que el hilo ejecutar para siempre, de manera similar a RunTaskCase2 Tampoco se recomienda .

Blog de referencia

Supongo que te gusta

Origin blog.csdn.net/zzti_erlie/article/details/113854085
Recomendado
Clasificación