Programación concurrente de Java 02: detener subprocesos con gracia

¡Acostúmbrate a escribir juntos! Este es el cuarto día de mi participación en el "Nuevo plan diario de los Nuggets·Desafío de actualización de abril", haz clic para ver los detalles del evento .

Introducción

Podemos iniciar el hilo llamando al start()método , pero detener el hilo es un poco más complicado.

Por lo general, en lugar de detener manualmente un subproceso, permitimos que el subproceso se ejecute hasta que finalice todo el proceso y luego dejamos que se detenga de forma natural. Sin embargo, para algunos casos especiales, el subproceso debe detenerse con anticipación, como que el usuario cierre repentinamente el programa, el programa se ejecute por error, etc. Los subprocesos que están a punto de detenerse en este momento siguen siendo valiosos en muchos escenarios comerciales, Javapero no brindan la capacidad de detener de forma directa y segura los subprocesos que son fáciles de usar.

JavaPara , la forma más correcta de detener un hilo es usar interrupt(), pero solo sirve como una notificación para que el hilo se detenga. Para el subproceso detenido, tiene plena autonomía y puede optar por detenerse inmediatamente, después de un período de tiempo o no detenerse en absoluto.

¿Por qué Javano proporcionar la capacidad de forzar la detención de un hilo?

  • JavaSe espera que los programas puedan notificarse entre sí y gestionar los subprocesos de forma cooperativa.
  • Porque si no sabe lo que está haciendo la otra parte, apresurarse a detener el hilo puede causar algunos problemas de seguridad.
  • Por ejemplo, cuando un subproceso está escribiendo un archivo, cuando recibe una señal de finalización, debe decidir de acuerdo con su propio negocio si detenerse inmediatamente o detenerse después de que todo el archivo se haya escrito con éxito, y si elige detener inmediatamente, puede causar confusión de datos. Ni el autor del comando de interrupción ni el receptor quieren problemas de datos

La forma incorrecta de detener un hilo

Las siguientes tres formas de detener un subproceso están marcadas como obsoletas @Deprecated:

  • stop(): detener la ejecución del subproceso. stop()El subproceso se detendrá directamente, aunque liberará el bloqueo, pero hará que la tarea se detenga abruptamente, lo que no le dará al subproceso suficiente tiempo para procesar el trabajo de finalización, y la integridad de los datos y otros problemas pueden ocurrir.
  • suspend()/ resume(): Suspender la ejecución del subproceso / Reanudar la ejecución del subproceso. suspend()Después de que el subproceso llama a , el bloqueo no se libera. resume()El bloqueo no se liberará hasta que se llame al subproceso . Esto puede conducir a problemas de interbloqueo.
  • Con la ayuda de volatilemarcadores

¿Por qué es incorrecto detener un hilo con la ayuda del bit de volatilebandera ?

En algunos casos, el volatilebit de bandera puede detener el subproceso correctamente, de la siguiente manera:

public class VolatileStopThread implements Runnable {
    private volatile boolean canceled = false;
	
    @Override
    public void run() {
        int num = 0;
        try {
            while (!canceled && num <= 1000000) {
                if (num % 10 == 0) {
                    System.out.println(num + "是10的倍数");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileStopThread r = new VolatileStopThread();
        Thread thread = new Thread(r);
        thread.start();
        Thread.sleep(3000);
        r.canceled = true;
	}
}
复制代码

Sin embargo, si el subproceso detenido se bloquea durante la ejecución (como agregar un elemento a una cola de bloqueo que ya está llena), entonces no podrá detectar que el bit de volatilebandera se ha establecido falsey no podrá salir de la cola. lazo. Por lo tanto, el hilo no se puede detener con la ayuda del bit de volatilebandera .

método interrupt() y bit indicador de interrupción

El interrupt()método puede detener el subproceso, que se divide en dos casos aquí:

  • Llamar a un interrupt()método establece el bit indicador de interrupción.
  • Llamar al interrupt()método , como el subproceso que llama al método sleep(), generará una excepción y wait(), al mismo tiempo, borrará el indicador de interrupción, es decir, lo establecerá en .join()InterruptedExceptionfalse

¿Cuáles son las formas de acceder a los bits de bandera de interrupción?

  • isInterrupted(): este método no afecta a las banderas de interrupción.
  • Método estático Thread.interrupted(): después de devolver el indicador de interrupción, se establecerá en false. Es decir, este método se puede utilizar para borrar el bit indicador de interrupción.

Detener el hilo correctamente

Método 1 : Declare el lanzamiento de una excepción en la firma run()del y utilícelo en el método para try/catchcapturar la excepción.

  • throws InterruptedException 标记方法,不捕获异常,以便该异常可以传递到顶层的 run() 方法
  • 由于 run() 方法无法抛出受检异常,所以只能使用 try/catch 捕获异常,这样的好处在于,顶层方法必须处理该异常

方式二:在 catch 块中再次中断线程。

  • catch 块中调用 Thread.currentThread().interrupt() 函数
  • 因为如果线程在休眠期间被中断,那么会自动清除中断信号
  • 这时如果手动添加中断信号,中断信号仍然可以被捕捉到

如果盲目地忽略中断请求,也就是屏蔽中断信号,可能会导致线程无法被正确停止。

两阶段终止模式

上面的方式二其实就是两阶段终止模式

两阶段终止模式 Two-Phase Termination Pattern 是一种多线程设计模式,用于在线程 t1 中优雅地停止另一个线程 t2,“优雅”指的是给被停止的线程做收尾工作的机会。

两阶段终止分为两种中断情况:

  • 运行时中断,打断标记已经为 true,只需要检测即可
  • 睡眠时中断,需要捕捉异常,以及重新设置打断标记为 true

团子注:使用 interrupt() 实现两阶段终止。关键点在于对于 sleepjoin 或者 wait 等处于阻塞状态的线程,interrupt() 只会抛出 InterruptedException,而不会设置打断标记,所以只要在异常处理内容中手动再次 interrupt() 重新设置打断标记即可。

流程图如下:

202204041124 Programación concurrente de Java 02: detener subprocesos correctamente 00.png

示例代码如下:

class TPTInterrupt {
	private Thread thread;
	
	public void start(){
		thread = new Thread(() -> {
			while(true) {
				Thread current = Thread.currentThread();
				if(current.isInterrupted()) {
					log.debug("料理后事");
					break;
				}
				try {
					Thread.sleep(1000);
					log.debug("将结果保存");
				} catch (InterruptedException e) {
					current.interrupt();
				}
				// 执行监控操作 
			}
		}, "监控线程");
		thread.start();
	}
	
	public void stop() {
		thread.interrupt();
	}
}
复制代码

调用:

TPTInterrupt t = new TPTInterrupt();
t.start();
Thread.sleep(3500);
log.debug("stop");
t.stop()
复制代码

结果:

11:49:42.915 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:43.919 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:44.919 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:45.413 c.TestTwoPhaseTermination [main] - stop 
11:49:45.413 c.TwoPhaseTermination [监控线程] - 料理后事
复制代码

参考资料

  • Pull Hook: JavaClase de programación concurrente 78Clase de 2parte
  • [[202112052113 subprocesamiento múltiple de Java: método de interrupción]]
  • [[202112052155 Patrones de diseño de subprocesos múltiples de Java: terminación en dos fases]]
  • [[202112052217 Java Multithreading: Método obsoleto en desuso]]

Supongo que te gusta

Origin juejin.im/post/7083519665414078471
Recomendado
Clasificación