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
相关推荐
ajsbxi5 分钟前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
StayInLove24 分钟前
G1垃圾回收器日志详解
java·开发语言
对许28 分钟前
SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder“
java·log4j
无尽的大道32 分钟前
Java字符串深度解析:String的实现、常量池与性能优化
java·开发语言·性能优化
小鑫记得努力41 分钟前
Java类和对象(下篇)
java
binishuaio1 小时前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE1 小时前
【Java SE】StringBuffer
java·开发语言
老友@1 小时前
aspose如何获取PPT放映页“切换”的“持续时间”值
java·powerpoint·aspose
颜淡慕潇1 小时前
【K8S问题系列 |1 】Kubernetes 中 NodePort 类型的 Service 无法访问【已解决】
后端·云原生·容器·kubernetes·问题解决
wrx繁星点点1 小时前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式