【Java笔记】多线程:一些有关中断的理解

发布于:2024-05-08 ⋅ 阅读:(24) ⋅ 点赞:(0)

线程中断的作用

线程中断可以使一个线程从等待状态变成就绪状态

使用线程中断,并不是要把线程给终止或是杀死,而是让线程不再继续等待,而是让线程不再继续等待,线程可以继续往下执行代码,线程发生中断后,会抛出一个中断的异常,决定如何处理就看业务代码怎么写

线程的等待状态

详细的JVM线程状态与OS线程状态相关可以看这篇,中断等待一般是从WAITING、TIME WAITING等阻塞,或者说挂起状态(感觉更准确,一般都是主动挂起,而BLOCK阻塞状态一般是竞争锁资源失败被动阻塞到队列中),恢复到RUNNABLE。

WAITING

  • Object.wait():使当前线程处于等待状态,直到另一个线程通过notify()显式唤醒它;
  • Thread.join():插队,当前线程需要等待join的线程执行完毕,底层调用的是Object实例的wait方法;
  • LockSupport.park():除非获得调用许可,否则禁用当前线程进行线程调度。LockSupport.unpark(), 恢复许可来唤醒

TIMED_WAITING

  • Thread.sleep(long millis):使当前线程睡眠指定时间

  • Object.wait(long timeout) :线程休眠指定时间,需要与synchronized一起使用,等待期间可以通过notify()/notifyAll()唤醒;

    Object o = new Object();
    synchronized (o){
        o.wait();//将当前线程挂起,并释放o锁
        o.notify();//唤醒一个等待在o锁等待队列的线程
        o.notifyAll();//唤醒等待在o锁等待队列的所有线程
    }
    
  • Thread.join(long millis):等待当前线程最多执行millis毫秒,如果millis为0,则会一直执行;

上述三种方法使线程等待后,可以通过Interrupt()显示唤醒(中断等待

线程从等待中恢复

  • 等待超时:Thread.sleep(long millis) 等到时间了自己回复
  • 得到通知:Object.wait() 线程休眠期间,其他线程可通过notify()/notifyAll()将其唤醒;
  • 使用中断:Interrupt()

java.lang.Thread中断实现

相关方法

  • **java.lang.Thread.interrupt() :**中断目标线程,给目标线程发一个中断信号,线程被打上中断标记(设为true)。

    public void interrupt() {
        if (this != Thread.currentThread()) {
            checkAccess();
    
            // thread may be blocked in an I/O operation
            synchronized (interruptLock) {
                Interruptible b = nioBlocker;
                if (b != null) {
                    interrupted = true;
                    interrupt0();  // inform VM of interrupt
                    b.interrupt(this);
                    return;
                }
            }
        }
        // 打上中断标记
        interrupted = true;
        // interrupt0()是一个native方法,主要就是用cpp从OS层面将线程唤醒
        interrupt0();  // inform VM of interrupt
    }
    
  • **java.lang.Thread.isInterrupted() :**判断目标线程是否被中断,不会清除中断标记。

    public boolean isInterrupted() {
        return interrupted;
    }
    
  • **java.lang.Thread.interrupted() :**判断目标线程是否被中断,会清除中断标记。

    public static boolean interrupted() {
        return currentThread().getAndClearInterrupt();
    }
    
    // ...
    boolean getAndClearInterrupt() {
        boolean oldValue = interrupted;
        // 因为读取interrupted字段时也可能被中断
        // 此时如果直接清除interrupted,会导致这次新的中断丢失
        // 所以这里要判断一下
        if (oldValue) {
            interrupted = false;
            clearInterruptEvent(); // native方法
        }
        return oldValue;
    }
    

中断标识interrupted

Thread中有一个中断标识属性volatile boolean interrupted ,用于标记当前线程是否中断。有两点要注意:

  • Thread.interrupt()方法做两个工作:
    • 在当前线程将中断标志修改为true,并不是停止线程
    • 通过native的interrupt0()将等待的线程唤醒
  • 如果当前线程在调用Object类的wait()方法或者这个类的join()sleep()方法被打断时,会将它的中断状态将被清除(interrupted设为false),并且它会收到一个InterruptedException异常。

当线程正在等待、休眠或以其他方式被占用,并且线程在活动之前或期间被中断抛出,都会抛出InterruptedException

  • (个人理解)抛出InterruptedException,这个行为是中断的直接结果,捕获这个异常后我们可以让线程执行唤醒后的逻辑(继续运行或者return结束)

    try{
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        // 打印日志或者return退出都可以
        // ...
    }
    // 当前线程的后续逻辑
    // ...
    
  • 线程通过抛出IE从非活跃状态(WAITING、TIMED WAITING)恢复到活跃状态(RUNNABLE)。如果线程本身就是活跃状态,则会无视RUNNABLE,也不会抛出IE。此时如果想处理中断,就需要**isInterrupted()interrupted()**来获取中断状态标识interrupted

一些小练习

Thread.interrupt() 只唤醒线程并修改中断标识

sleep、wait、join挂起线程都会修改中断标识符并抛出InterruptedException,如果没有抛出IE的情况下(比如yield后线程还是RUNNABLE状态)希望响应中断,则需要isInterrupted()interrupted()来获取中断状态标识interrupted

private static void test1(){
    // 程序中没有响应中断信号的逻辑,线程不会被中断
    Thread thread = new Thread(()->{
        System.out.println("线程启动");
        while (true){
            Thread.yield();
        }
    });
    thread.start();
    thread.interrupt();
}
private static void test2(){
		// 手动响应中断,退出线程
    Thread thread = new Thread(()->{
        System.out.println(Thread.currentThread().getName()+": 线程启动");
        while (true){
            Thread.yield();
            // 响应中断
            if (Thread.currentThread().isInterrupted()){
                System.out.println(Thread.currentThread().getName()+": 线程被中断");
                return;
            }
        }
    });
    thread.start();
    thread.interrupt();
}

sleep() 清除中断状态标识

private static void test3() throws InterruptedException {
    // 中断失败,因为sleep会将当前线程的interrupted清楚(设为false)
    Thread thread = new Thread(()->{
        System.out.println(Thread.currentThread().getName()+": 线程启动");
        while (true){
            // 响应中断
            if (Thread.currentThread().isInterrupted()){
                System.out.println(Thread.currentThread().getName()+": 线程被中断,程序退出");
                return;
            }
            try{
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName()+": 线程休眠被中断");
            }
        }
    });
    thread.start();
    Thread.sleep(2000);
    thread.interrupt();
}
  • 最开始,子线程start,Thread.currentThread().isInterrupted() 为false,不能进入if(Thread.currentThread().isInterrupted())直接进入try里的sleep(3s)
  • 主线程sleep(2s)
  • 2s后,主线程自己到点唤醒,通过thread.interrupt() 中断唤醒子线程,此时中断标识interrupted为true,由于sleep()抛出InterruptedException(同时将中断标识清除,设为false)被catch捕获,打印“线程休眠被中断”
  • 回到if (Thread.currentThread().isInterrupted()),由于前面sleep抛出IE的同时将中断标识设为false,所以不能执行这个if里的逻辑,结果就是一直在while里重复sleep

在这里插入图片描述

private static void test4() throws InterruptedException {
    Thread thread = new Thread(() -> {
        System.out.println(Thread.currentThread().getName()+": 线程启动");
        while (true){
            // 响应中断
            if (Thread.currentThread().isInterrupted()){
                System.out.println(Thread.currentThread().getName()+": 线程被中断,程序退出");
                return;
            }
            try{
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName()+": 线程休眠被中断");
                Thread.currentThread().interrupt();
            }
        }
    });
    thread.start();
    Thread.sleep(2000);
    thread.interrupt();
}

这里再catch到sleep的IE之后再通过Thread.currentThread().interrupt() 把当前线程的interrupted 表示符置为true,下一个循环就能执行if (Thread.currentThread().isInterrupted()) 的return逻辑了。
在这里插入图片描述
为什么呢?

public static void main(String[] args) {
    System.out.println(Thread.currentThread().isInterrupted());
    Thread.currentThread().interrupt();
    try {
        System.out.println(LocalTime.now() +": 开始睡眠");
        System.out.println(Thread.currentThread().isInterrupted());
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        System.out.println(Thread.currentThread().isInterrupted());
        System.out.println(LocalTime.now()+": 发生中断");
    }

    System.out.println(LocalTime.now()+": 结束睡眠");
}

在这里插入图片描述

打印一下中断标识,可以看出sleep之前interrupt()已经将interrupted设为true,sleep会将其改为false并直接抛出IE

Reference

【Java并发·08】线程中断 interrupt
对于Java线程中断的理解,哪种情况下会响应中断?哪种情况下不响应中断?
Java 线程中断?看这篇就够了