线程中断
什么是线程中断
线程中断是一种协商机制
。
一个线程不应该由其他线程来强制中断或停止,应该是由线程自己自行停止
。
在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