JUC进阶01—— 线程中断

线程中断

什么是线程中断

线程中断是一种协商机制

一个线程不应该由其他线程来强制中断或停止,应该是由线程自己自行停止

在Java中没有办法立即停止一个线程,但是停止线程是尤为重要的,比如取消某个非常耗时的操作。因此Java提供了一种停止线程的 协商 机制 ------ 中断 ,也就是 中断标识协商机制

  • 中断只是一种协商机制,Java 中没有给中断增加任何语法,终端的过程完全需要要程序员自行实现。若要中断一个线程,需要手动调用线程的 interrupt 方法,该方法也仅仅是将该线程对象的中断标识设置为 true,接着只需要不断检测当前线程的标识位,若为 true ,表示别的线程请求当前线程中断。

  • 每个线程都有一个中断标识位,用于表示线程是否被中断;若标识位为 true 表示中断,为false 表示未中断;通过调用线程对象的 interrupt 方法将该线程的标识位设置为 true ,可以在其他线程中调用,也可以在当前线程中调用

中断的相关API方法之三大方法说明及使用

三大方法说明

  • public void interrupt()
    • 实例方法( Just to set the interrupt flag ) 仅仅是设置线程的中断状态为true发起一个协商而不会立刻停止线程
java 复制代码
/**
 * 1.除非当前线程正在中断自身,这是始终允许的,否则将调用此线程的checkAccess()方法,这可能会导致抛出SecurityException
 *
 * 2.如果此线程在调用Object类的wait()、wait(long)或wait(long, int)方法,
 * 或者调用本类的join()、join(long)、join(long, int)、sleep(long)或sleep(long, int)方法时被阻塞,那么它的中断状态将被清除,并且它将收到一个InterruptedException
 *
 * 3.如果此线程在java.nio.channels.InterruptibleChannel上进行I/O操作时被阻塞,那么该通道将被关闭,线程的中断状态将被设置,
 * 并且线程将收到java.nio.channels.ClosedByInterruptException
 *
 * 4. 如果以上条件都不满足,那么此线程将被设置为中断状态
 * 
 * 5.如果线程已经死亡,那么中断它不会有任何效果。也就是说,即使尝试中断一个已经结束的线程,也不会产生任何影响。
 * 这是因为线程的生命周期已经结束,无法再被调度执行任务
 * @throws  SecurityException
 * if the current thread cannot modify this thread
 * 如果当前线程不能修改这个线程,那么会抛出一个`SecurityException`异常
 * @revised 6.0
 * @spec JSR-51
 */

public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}
  • public static boolean interrupted()
    • 静态方法 Thread.interrupted();
    • 判断线程是否被中断并清除当前中断状态(做了两件事情:判断、清除)
      • 1.返回当前线程的中断状态,测试当前线程是否已经被中断
      • 2.将当前线程的中断状态清零并重新设置为false,清除线程的中断状态
      • 3.如果连续两次调用此方法,第二次会返回false,因为连续调用两次的结果可能不一样
Java 复制代码
/**
 * 测试当前线程是否已被中断。该方法会清除线程的中断状态。
 * 换句话说,如果这个方法连续被调用两次,第二次调用将返回false(除非在第一次调用清除中断状态后,在第二次调用检查之前,当前线程再次被中断)。
 *
 * <p>如果线程在中断时未存活,那么被忽略的中断将反映在这个方法返回false。
 * @see #isInterrupted()
 * @revised 6.0
 */
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}
  • public boolean isInterrupted()
    • 实例方法 :判断当前线程是否被中断(通过检查中断标志位)
typescript 复制代码
/**
 * 测试此线程是否已被中断。此方法不会影响线程的<i>中断状态</i>。  
 *
 * 如果线程在中断时未存活,则被忽略的中断将由该方法返回false来反映。  
 * @see     #interrupted()
 * @revised 6.0
 */
public boolean isInterrupted() {
    return isInterrupted(false);
}

线程停止、中断案例

如何停止中断运行中的线程?

通过一个volatile变量实现

csharp 复制代码
public class InterruptDemo {
    //volatile修饰的变量具有可见性
    private static volatile boolean stopFlag = false;
    
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true){
                if(stopFlag){
                    System.out.println(Thread.currentThread().getName()+" \t"+"中断标识被修改,此线程停止运行");
                    break;
                }
                System.out.println(Thread.currentThread().getName()+" \t"+"平稳运行");
            }

        });
        try {
            TimeUnit.MILLISECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.start();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+" \t"+"修改中断标识");
            stopFlag = true;
        },"t2").start();
    }
}

运行结果

Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
t2 	修改中断标识
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	中断标识被修改,此线程停止运行

通过 atomicBoolean 实现

java 复制代码
public class InterruptDemo {
    //volatile修饰的变量具有可见性
    static AtomicBoolean stopFlag = new AtomicBoolean(false);
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true){
                if(stopFlag.get()){
                    System.out.println(Thread.currentThread().getName()+" \t"+"中断标识被修改,此线程停止运行");
                    break;
                }
                System.out.println(Thread.currentThread().getName()+" \t"+"平稳运行");
            }

        });
        try {
            TimeUnit.MILLISECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.start();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+" \t"+"修改中断标识");
            stopFlag.set(true);
        },"t2").start();
    }
}

运行结果

Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
t2 	修改中断标识
Thread-0 	平稳运行
Thread-0 	中断标识被修改,此线程停止运行

通过Thread类自带的中断API实例方法实现----在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑stop线程

csharp 复制代码
public class InterruptDemo {
    //volatile修饰的变量具有可见性
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true){
                if(Thread.currentThread().isInterrupted()){
                    System.out.println(Thread.currentThread().getName()+" \t"+"isInterrupted 值被修改,此线程停止运行");
                    break;
                }
                System.out.println(Thread.currentThread().getName()+" \t"+"平稳运行");
            }

        });
        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.start();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"把t1 isInterrupted 值被修改为true ");
            //设置线程的中断标识为 true
            t1.interrupt();
        },"t2").start();
    }
}

运行结果

Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
Thread-0 	平稳运行
t2把 t1 isInterrupted 值被修改 
Thread-0 	平稳运行
Thread-0 	isInterrupted 值被修改,此线程停止运行
  • 当前线程的中断标识为true,是不是线程就立刻停止
    答案是 不立刻停止,具体来说,当对一个线程,调用interrupt时
    • 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,仅此而已,被设置中断标志的线程将继续正常运行,不受影响,所以interrupt()并不能真正的中断线程,需要被调用的线程自己进行配合才行,对于不活动的线程没有任何影响
    • 如果线程处于阻塞状态(例如sleep,wait,join状态等),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态(interrupt状态也将被清除),并抛出一个InterruptedException异常

第一种情况:正常活动状态演示

java 复制代码
/**
 * 执行interrupt方法将t1标志位设置为true后,t1没有中断,仍然完成了任务后再结束
 * 在2000毫秒后,t1已经结束称为不活动线程,设置状态为没有任何影响
 */
public class InterruptDemo {
    //volatile修饰的变量具有可见性
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            for (int i = 1; i <=200 ; i++) {
                System.out.println("===========  "+i);
            }
            
    
            /**
             * ===========  295
             * ===========  296
             * ===========  297
             * ===========  298
             * ===========  299
             * ===========  300
             * t1线程调用interrupt后的中断标识位02:true
             */
            System.out.println("t1线程调用interrupt后的中断标识位02:"+ Thread.currentThread().isInterrupted());
        });
        
        t1.start();
        System.out.println("t1线程默认的中断标识位:"+ t1.isInterrupted());
        try {
            TimeUnit.MILLISECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
        
        /**
        * ===========  48
        * ===========  49
        * t1线程调用interrupt后的中断标识位01:true
        */
        System.out.println("t1线程调用interrupt后的中断标识位01:"+ t1.isInterrupted());

        try {
            TimeUnit.MILLISECONDS.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //  2000毫秒后,t1线程已经不活动了,不会产生任何影响
        System.out.println("t1线程调用interrupt()后的中断标志位03:" + t1.isInterrupted());//false

    }
}

执行结果

ini 复制代码
t1线程默认的中断标识位:false
===========  1

-----此处顺序输出 2------47 的日志输出省略-----
	.
	.
	.
		
===========  48
===========  49
t1线程调用interrupt后的中断标识位01:true
===========  50
===========  51
===========  52
===========  53

-----此处顺序输出 54------298 的日志输出省略-----
	.
	.
	.
        
===========  299
===========  300
t1线程调用interrupt后的中断标识位02:true
t1线程调用interrupt()后的中断标志位03:false ----此时t1线程已经停止运行了,不会产生任何影响

第二种情况:处于阻塞状态演示

java 复制代码
public class InterruptDemo2 {
    public static void main(String[] args) {
        /**
         * 对 t1 线程发起中断协商,此时 t1线程不会立即停止, 在 发起了中断协商后, t1线程执行到 Thread.sleep 触发
         *  java.lang.InterruptedException: sleep interrupted 并清除掉之前的 中断标识
         *  导致线程不会停止while(true)死循环 ,一直运行
         *
         *  解决方法:在捕获异常的 catch块中再加重新设置 中断标识即可
         */
        Thread t1 = new Thread(()->{
            while (true){
                if(Thread.currentThread().isInterrupted()){
                    System.out.println("t1 线程中断标识 被修改为 true, t1 线程停止运行");
                    break;
                }
                //sleep方法抛出InterruptedException后,中断标识也被清空置为false,如果没有在
                //catch方法中执行 Thread.currentThread().interrupt() 去调用interrupt方法再次将中断标识置为true,这将导致无限循环了
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    //Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
                System.out.println("t1 线程正在平稳运行");
            }
        });
        t1.start();

        new Thread(t1::interrupt,"t2").start();
    }
}

执行结果:

css 复制代码
D:\Develop\JDK\JDK1.8\jdk\bin\java.exe -javaagent:D:\Develop\IDE\IntelliJIDEA\lib\idea_rt.jar=50854:D:\Develop\IDE\IntelliJIDEA\bin -Dfile.encoding=UTF-8 -classpath D:\Develop\JDK\JDK1.8\jdk\jre\lib\charsets.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\deploy.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\ext\access-bridge-64.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\ext\cldrdata.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\ext\dnsns.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\ext\jaccess.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\ext\jfxrt.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\ext\localedata.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\ext\nashorn.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\ext\sunec.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\ext\sunjce_provider.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\ext\sunmscapi.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\ext\sunpkcs11.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\ext\zipfs.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\javaws.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\jce.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\jfr.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\jfxswt.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\jsse.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\management-agent.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\plugin.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\resources.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\rt.jar;D:\Develop\JDK\JDK1.8\jdk\jre\lib\tools.jar;E:\software_data\IDE_data\IDEA_workspace\zero\zero_demo\target\classes;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\boot\spring-boot-starter-web\2.3.4.RELEASE\spring-boot-starter-web-2.3.4.RELEASE.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\boot\spring-boot-starter\2.6.12\spring-boot-starter-2.6.12.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\boot\spring-boot\2.6.12\spring-boot-2.6.12.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\boot\spring-boot-autoconfigure\2.6.12\spring-boot-autoconfigure-2.6.12.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\boot\spring-boot-starter-logging\2.6.12\spring-boot-starter-logging-2.6.12.jar;E:\software_data\maven_data\IDEA_Repository\springboot\ch\qos\logback\logback-classic\1.2.11\logback-classic-1.2.11.jar;E:\software_data\maven_data\IDEA_Repository\springboot\ch\qos\logback\logback-core\1.2.11\logback-core-1.2.11.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\slf4j\slf4j-api\1.7.36\slf4j-api-1.7.36.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\apache\logging\log4j\log4j-to-slf4j\2.17.2\log4j-to-slf4j-2.17.2.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\apache\logging\log4j\log4j-api\2.17.2\log4j-api-2.17.2.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\slf4j\jul-to-slf4j\1.7.36\jul-to-slf4j-1.7.36.jar;E:\software_data\maven_data\IDEA_Repository\springboot\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\spring-core\5.3.23\spring-core-5.3.23.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\spring-jcl\5.3.23\spring-jcl-5.3.23.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\yaml\snakeyaml\1.29\snakeyaml-1.29.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\boot\spring-boot-starter-json\2.6.12\spring-boot-starter-json-2.6.12.jar;E:\software_data\maven_data\IDEA_Repository\springboot\com\fasterxml\jackson\core\jackson-databind\2.13.4\jackson-databind-2.13.4.jar;E:\software_data\maven_data\IDEA_Repository\springboot\com\fasterxml\jackson\core\jackson-annotations\2.13.4\jackson-annotations-2.13.4.jar;E:\software_data\maven_data\IDEA_Repository\springboot\com\fasterxml\jackson\core\jackson-core\2.13.4\jackson-core-2.13.4.jar;E:\software_data\maven_data\IDEA_Repository\springboot\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.13.4\jackson-datatype-jdk8-2.13.4.jar;E:\software_data\maven_data\IDEA_Repository\springboot\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.13.4\jackson-datatype-jsr310-2.13.4.jar;E:\software_data\maven_data\IDEA_Repository\springboot\com\fasterxml\jackson\module\jackson-module-parameter-names\2.13.4\jackson-module-parameter-names-2.13.4.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\boot\spring-boot-starter-tomcat\2.6.12\spring-boot-starter-tomcat-2.6.12.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\apache\tomcat\embed\tomcat-embed-core\9.0.65\tomcat-embed-core-9.0.65.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\apache\tomcat\embed\tomcat-embed-el\9.0.65\tomcat-embed-el-9.0.65.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.65\tomcat-embed-websocket-9.0.65.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\spring-web\5.3.23\spring-web-5.3.23.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\spring-beans\5.3.23\spring-beans-5.3.23.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\spring-webmvc\5.3.23\spring-webmvc-5.3.23.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\spring-aop\5.3.23\spring-aop-5.3.23.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\spring-context\5.3.23\spring-context-5.3.23.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\springframework\spring-expression\5.3.23\spring-expression-5.3.23.jar;E:\software_data\maven_data\IDEA_Repository\springboot\junit\junit\4.12\junit-4.12.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\hamcrest\hamcrest-core\2.2\hamcrest-core-2.2.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\hamcrest\hamcrest\2.2\hamcrest-2.2.jar;E:\software_data\maven_data\IDEA_Repository\springboot\org\projectlombok\lombok\1.18.24\lombok-1.18.24.jar com.avgrado.demo.thread.InterruptDemo2
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.avgrado.demo.thread.InterruptDemo2.lambda$main$0(InterruptDemo2.java:23)
	at java.lang.Thread.run(Thread.java:745)

t1 线程正在平稳运行
t1 线程正在平稳运行
t1 线程正在平稳运行
t1 线程正在平稳运行
t1 线程正在平稳运行
t1 线程正在平稳运行
t1 线程正在平稳运行
t1 线程正在平稳运行
t1 线程正在平稳运行
t1 线程正在平稳运行
t1 线程正在平稳运行

----死循环----
....

源码中对阻塞情况导致原因的阐述

总之,需要记住的是中断只是一种协商机制,修改中断标识位仅此而已,不是立刻stop打断

静态方法Thread.interrupted()的理解

对于静态方法Thread.interrupted()演示

java 复制代码
public static void main(String[] args) {
  /**
   * main  false
   * main  false
   * -----------1
   * -----------2
   * main  true
   * main  false
   */
  System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//false
  System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//false
  System.out.println("-----------1");
  Thread.currentThread().interrupt();
  System.out.println("-----------2");
  System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//true
  System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//false

}

对于静态方法Thread.interrupted()和实例方法isInterrupted()区别在于:

  • 静态方法interrupted将会清除中断状态(传入的参数ClearInterrupted为true)
  • 实例方法isInterrupted则不会(传入的参数ClearInterrupted为false)

线程中断机制总结

  • public void interrupt() 是一个实例方法,它通知目标线程中断,也仅仅是设置目标线程的中断标志位为true
  • public boolean isInterrupted() 是一个实例方法,它判断当前线程是否被中断(通过检查中断标志位)并获取中断标志
  • public static boolean interrupted() 是一个静态方法,返回当前线程的中断真实状态(boolean类型)后会将当前线程的中断状态设为false,此方法调用之后会清除当前线程的中断标志位的状态(将中断标志置为false了),返回当前值并清零置为false
相关推荐
计算机毕设指导63 分钟前
基于 SpringBoot 的作业管理系统【附源码】
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
Gu Gu Study4 分钟前
枚举与lambda表达式,枚举实现单例模式为什么是安全的,lambda表达式与函数式接口的小九九~
java·开发语言
Chris _data7 分钟前
二叉树oj题解析
java·数据结构
牙牙70513 分钟前
Centos7安装Jenkins脚本一键部署
java·servlet·jenkins
paopaokaka_luck20 分钟前
[371]基于springboot的高校实习管理系统
java·spring boot·后端
以后不吃煲仔饭33 分钟前
Java基础夯实——2.7 线程上下文切换
java·开发语言
进阶的架构师34 分钟前
2024年Java面试题及答案整理(1000+面试题附答案解析)
java·开发语言
The_Ticker39 分钟前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
大数据编程之光1 小时前
Flink Standalone集群模式安装部署全攻略
java·大数据·开发语言·面试·flink
爪哇学长1 小时前
双指针算法详解:原理、应用场景及代码示例
java·数据结构·算法