Java multithreaded concurrency (4) interrupt

Outage Overview

In Java multi-threading, threads hope not to be interrupted by other threads, so they are Thead.stop、Thead.suspend、Thread.resumeall discarded. Java has no way to stop a thread immediately, but we want to stop a thread at an important moment, such as taking time-consuming operations, so Java provides A negotiation mechanism for stopping threads is introduced: interrupt.

But interrupts are not the syntax of Java, but artificially constructed, and you need to write interrupts yourself. Each thread object has an interrupt flag , and interruptthe method sets the thread ID to true, and you can set your own thread interrupt flag in any thread.

That is to say, Java provides a platform for implementing interrupts, but it does not provide a complete interrupt process mechanism.

API method

ThreadIn class:

method effect
public void interrupt() Set the interrupt flag of the current thread to true, but not immediately interrupt the current thread.
public boolean isInterrupted() Determine whether the current thread is interrupted by the thread interrupt flag bit
public static boolean interrupted() Determine whether the thread is interrupted by the thread state, and reset the interrupt flag.

It can be seen from the API that Java does not have a method of forced interruption, so we manually interrupt a thread.

interrupt()

In interrupt()the bottom layer of the method, it calls a interrupt0()local method named , which explains in the comments that the interrupt flag is set, but no interrupt action is performed.

isInterrupted()

This method is used to determine whether the thread has set the interrupt flag. The local method with the same name is called in the bottom layer, and a parameter passed in boolean ClearInterruptedis by default fales, that is, the interrupt flag is not cleared by default.

interrupted()

This method call isInterrupted(true), while accessing the interrupt flag bit, will reset the flag bit.

Three ways to implement interrupts

volatile keyword

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()方法时,主线程会等待第一个任务执行完毕并释放锁之后才执行第二个任务,因此不会出现线程阻塞的情况。

Guess you like

Origin juejin.im/post/7230307122415714360