Java多线程并发(4)中断

中断概述

在Java多线程中,线程希望不被其他线程所打断,因此 Thead.stop、Thead.suspend、Thread.resume都被废弃了,Java没有办法立即停止一个线程,而在重要时刻我们希望停止某个线程,如取消耗时操作,因此Java提供了一种停止线程的协商机制:中断。

但是中断并不是Java的语法,而是人为构建,需要自己编写中断。每个线程对象都有一个中断标志,调用对象的 interrupt方法将线程的标识设置为 true,可以在任何线程中设置自己的线程中断标志。

也就是说Java提供了一个实现中断的平台,但是没有提供完整的中断流程机制。

API方法

Thread类中:

方法 作用
public void interrupt() 设置当前线程的中断标志为true,但是不是立马就中断当前线程。
public boolean isInterrupted() 通过线程中断标志位判断当前线程是否中断
public static boolean interrupted() 通过线程状态判断线程是否中断,并且重置中断标志位。

从API中就可以看出,Java并没有强制中断的方法,所以真正中断一个线程是我们手动实现的。

interrupt()

interrupt()方法的底层中,它调用了一个名为interrupt0()的本地方法,该方法在注释中说明了是设置中断标志位,而没有进行中断动作。

isInterrupted()

该方法用于判断线程是否设置了中断标志位。 底层中调用了本地同名方法方法,并传入的一个参数boolean ClearInterrupted默认为fales,即默认不清除中断标志位。

interrupted()

该方法调用isInterrupted(true),访问中断标志位的同时,会重置标志位。

实现中断的三个方法

volatile关键字

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

猜你喜欢

转载自juejin.im/post/7230307122415714360
今日推荐