Interrupción de concurrencia multiproceso de Java (4)

Resumen de cortes

En los subprocesos múltiples de Java, los subprocesos esperan no ser interrumpidos por otros subprocesos, por lo que Thead.stop、Thead.suspend、Thread.resumetodos descartan. Java no tiene forma de detener un subproceso de inmediato, pero en momentos importantes queremos detener un subproceso, como realizar operaciones que consumen mucho tiempo. , por lo que Java proporciona Se introduce un mecanismo de negociación para detener subprocesos: interrupción.

Pero las interrupciones no son la sintaxis de Java, sino construidas artificialmente, y usted mismo debe escribir las interrupciones. Cada objeto de subproceso tiene un indicador de interrupción , y interruptel método establece el ID del subproceso en true, y puede establecer su propio indicador de interrupción de subproceso en cualquier subproceso.

Es decir, Java proporciona una plataforma para implementar interrupciones, pero no proporciona un mecanismo de proceso de interrupción completo.

método API

ThreadEn la clase:

método efecto
interrupción de vacío público () Establezca el indicador de interrupción del subproceso actual en verdadero, pero no interrumpa inmediatamente el subproceso actual.
booleano público está interrumpido () Determinar si el subproceso actual es interrumpido por el bit indicador de interrupción del subproceso
booleano estático público interrumpido () Determine si el subproceso está interrumpido por el estado del subproceso y restablezca el indicador de interrupción.

Se puede ver en la API que Java no tiene un método de interrupción forzada, por lo que interrumpimos manualmente un hilo.

interrumpir()

En interrupt()la capa inferior del método, llama a un interrupt0()método local llamado , que explica en los comentarios que el indicador de interrupción está establecido, pero no se realiza ninguna acción de interrupción.

está interrumpido ()

Este método se usa para determinar si el subproceso ha establecido el indicador de interrupción. El método local con el mismo nombre se llama en la capa inferior, y se pasa un parámetro boolean ClearInterruptedde forma predeterminada fales, es decir, el indicador de interrupción no se borra de forma predeterminada.

interrumpido()

Esta llamada de método isInterrupted(true), al acceder al bit indicador de interrupción, restablecerá el bit indicador.

Tres formas de implementar interrupciones

palabra clave volátil

static volatile boolean isStop = false;
public static void main(String[] args) throws InterruptedException  {
    new Thread(() -> {
        while(true){
            if (isStop){
                System.out.println("线程1被停止了");
                break;
            }
             System.out.println("running");
        }
    }).start();
    TimeUnit.SECONDS.sleep(2);
    new Thread(() -> {
        isStop = true;
    }).start();
}
复制代码

在这里的 volatile关键字对 isStop属性修饰的作用有两个:

  1. 可见性

在多线程环境中,当一个线程修改一个共享变量时,如果没有使用volatile关键字进行修饰,其他线程可能无法立即看到最新值,因为线程之间的交互可能存在缓存一致性问题。而使用volatile修饰后,写操作会立即更新到主内存中,读操作也会直接从主内存中读取最新值,从而实现了可见性。(JVM中解释一个方法的局部变量表存储的外部变量是复制副本,所以进行的修改需要写到主内存中才能生效。)

  1. 有序性

在JVM优化过程中,为了提高程序运行效率,编译器和CPU可能对代码执行顺序进行优化,导致指令交错或者重排,从而改变了代码实际执行的顺序,这可能会导致多线程程序的错误。使用volatile关键字可以禁止指令重排,保证指令执行的有序性,从而避免了由于指令重排而引起的线程安全问题

我们把这里的 volatile关键字去掉也能让线程中断,但是对于CPU来说,它并不是同步修改 isStop,因此在执行中存在延迟,使得中断延迟了。

AtomicBoolean类

static AtomicBoolean isStop = new AtomicBoolean(false);
public static void main(String[] args) throws InterruptedException  {
    new Thread(() -> {
        while(true){
            if (isStop.get()){
                System.out.println("线程1被停止了");
                break;
            }
             System.out.println("running");
        }
    }).start();
    TimeUnit.SECONDS.sleep(2);
    new Thread(() -> {
        isStop.set(true);
    }).start();
}
复制代码

那么为什么 AtomicBoolean类能够实现可见性呢?

原来在它的底层代码中:属性 private volatile in tvalue;也是volatile修饰的。

利用Thread中的方法

public static void main(String[] args) throws InterruptedException  {
    Thread t1 = new Thread(() -> {
        while(true){
            if (Thread.currentThread().isInterrupted()){
                System.out.println("线程1被停止了");
                break;
            }
            System.out.println("running");
        }
    });
    t1.start();
    TimeUnit.SECONDS.sleep(2);
    new Thread(() -> {
        t1.interrupt();
    }).start();
}
复制代码

当一个线程调用 interrupt()的时候,如果线程处于正常状态,那么仅仅是把线程的中断标志位设置为 true,线程不会被影响

如果线程处于阻塞状态 sleep\wait\join等,调用这个方法,会立马退出阻塞状态,并抛出 interruptException

上期疑惑解答

第一个CompletableFuture调用了join()方法,而第二个CompletableFuture没有调用join()方法。当调用join()方法时,主线程会等待异步任务执行完成,直到CompletableFuture中的任务执行完毕才会继续往后执行。如果注释掉join()方法,就相当于让主线程不等待第一个异步任务的完成,直接执行第二个异步任务,因此会出现线程阻塞的情况。

具体来说,当两个异步任务同时竞争lock锁时,第一个任务先获取到了锁并执行了1秒钟,然后休眠了2秒钟。此时,如果注释掉第一个任务的join()方法,主线程会立即执行第二个任务,但是由于第一个任务还持有锁,并且正在休眠状态中,因此第二个任务无法获取到锁,会一直等待第一个任务释放锁。而第一个任务又被阻塞在休眠状态中,因此无法释放锁,导致第二个任务一直处于等待状态,从而出现线程阻塞的情况。

相反,当保留第一个任务的join()方法时,主线程会等待第一个任务执行完毕并释放锁之后才执行第二个任务,因此不会出现线程阻塞的情况。

Supongo que te gusta

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