文章目录
- 1、线程中断机制
- 2、三大中断方法的说明
- 3、通过volatile变量实现线程停止
- 4、通过AtomicBoolean实现线程停止
- 5、通过Thread类的interrupt方法实现线程停止
- 6、interrupt和isInterrupted方法源码
- 7、interrupt方法注意点
- 8、静态方法interrupted的注意点
1、线程中断机制
一个线程不应该由其他线程来强制停止,而应该由线程自己来决定是否停止,因此:
//已过时
Thread.stop
Thread.suspend
Thread.resume
这三个方法已废弃。但如果一个线程确实需要停止,比如取消一个耗时任务,因此针对停止线程,Java提供中断标识协商机制。关键字:
- 自己决定
- 中断标识
- 只是协商,不是强制,不会里了停止线程,是我希望你能停止
最后,中断这种协商机制,Java没有给具体语法或者关键字,中断逻辑是开发者自己根据实际需求实现的。每个线程对象都有一个中断标识位,true表示中断,false未中断。此时,写代码去不断检测当前线程的标识位,检测到true,则表示其他线程请求中断这条线程(注意,只是代表别人请求中断)。
因此,想中断一个线程,可手动调用该线程的interrupt方法(可以自己调,也可以别的线程调),该方法仅仅是将线程对象的中断标识设为true。
2、三大中断方法的说明
搜索java.lang.Thread类下的方法,关于中断有:
1、public void interrupt()
- 特点:实例方法,作用于调用对象线程
- 作用:仅仅将线程调用对象线程的中断状态设置为true(注意是协商,只是标志位变了,线程不会立刻停止)
2、public static boolean interrupted()
- 特点:静态方法,作用于当前线程
- 作用:a、返回当前线程的中断状态 b、将当前线程的中断状态重新设为false ,类似i++,先返回i,再做自增1
3、public boolean isInterrupted()
- 特点:实例方法,作用于调用对象线程
- 作用:看看线程的中断标志位是true还是false
3、通过volatile变量实现线程停止
引入一个自定义的标志位:
public class InterruptDemo1 {
static volatile boolean isStop = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (true) {
if (isStop) {
System.out.println("标识位为true,有线程请求停止本线程,即将主动停止,保存数据中....");
break;
}
System.out.println(Thread.currentThread().getName() + "正在运行任务...");
}
}, "t1").start();
//20ms后启动线程t2,去把自定义的标志位改为true
TimeUnit.MILLISECONDS.sleep(20);
new Thread(() -> {
isStop = true;
}, "t2").start();
}
}
4、通过AtomicBoolean实现线程停止
同引入一个自定义标志位:
public class InterruptDemo1 {
static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (true) {
if (atomicBoolean.get()) {
System.out.println("标识位为true,有线程请求停止本线程,即将主动停止,保存数据中....");
break;
}
System.out.println(Thread.currentThread().getName() + "正在运行任务...");
}
}, "t1").start();
//20ms后启动线程t2,去把自定义的标志位改为true
TimeUnit.MILLISECONDS.sleep(20);
new Thread(() -> {
atomicBoolean.set(true);
}, "t2").start();
}
}
5、通过Thread类的interrupt方法实现线程停止
使用:
- Thread.currentThread().isInterrupted()查看当前线程标志位
- t1.interrupt改变标志位
public class InterruptDemo1 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("当前线程的默认中断标志位是:" + Thread.currentThread().isInterrupted());
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("标识位为true,有线程请求停止本线程,即将主动停止,保存数据中....");
break;
}
System.out.println(Thread.currentThread().getName() + "正在运行任务...");
}
}, "t1");
t1.start();
//20ms后启动线程t2,去把自定义的标志位改为true
TimeUnit.MILLISECONDS.sleep(20);
new Thread(() -> {
t1.interrupt();
}, "t2").start();
}
}
自己来中断:
public class InterruptDemo1 {
static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("当前线程的默认中断标志位是:" + Thread.currentThread().isInterrupted());
int i = 1;
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("标识位为true,有线程请求停止本线程,即将主动停止,保存数据中....");
break;
}
System.out.println(Thread.currentThread().getName() + "正在运行任务...");
i++;
if (i == 6) {
//自己来改标志位
Thread.currentThread().interrupt();
}
}
}, "t1");
t1.start();
}
}
6、interrupt和isInterrupted方法源码
interrupt方法源码:checkAccess方法往下可能抛出SecurityException异常,即当前线程无法修改此线程
不管调用interrupt的线程对象是不是当前线程对象,都调了interrupt0,跟进interrupt0,native即说明这里是JVM在调底层操作系统或者第三方C的函数库
isInterrupted方法,底层调一个native方法,中断状态是否重置取决于传的值,isInterrupt传false,因此isInterrupted方法只是看看线程对象的标志位是啥。
7、interrupt方法注意点
当对一个线程t 调用interrupt方法:
- 如果线程t 处于正常活动状态,则将其标志位设置为true,仅此而已,t线程继续正常运行,不受影响
- 如果线程t 调用了wait、join、sleep方法而被阻塞,调用interrupt就会清除中断状态,并抛出InterruptedException异常
- 如果线程t 已经处于不活动状态,或者说是run方法运行完,进入死亡状态了,那中断这个线程不会产生任何影响
先看下中断一个不活动的线程:
public class InterruptDemo2 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 300; i++) {
System.out.println("----" + i);
}
}, "t1");
t1.start();
System.out.println("t1的默认中断标志位:" + t1.isInterrupted());
TimeUnit.MILLISECONDS.sleep(2);
t1.interrupt();
System.out.println("main线程中更改了t1的标志位,结果:" + t1.isInterrupted());
TimeUnit.MILLISECONDS.sleep(2000);
System.out.println("t1线程进入死亡状态后再次获取其标志位,结果:" + t1.isInterrupted());
}
}
此时,t1线程已经属于不活动的线程,或者说run方法执行结束了,线程进入死亡状态,此时标志位为false。连续重复中断,或者线程进入死亡状态后再改中断标志位等操作,做了等于没做。
再试试中断一个wait/join/sleep被阻塞的线程:
public class Interrupt3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("标识位变更为true,有线程请求停止本线程,即将主动停止,保存数据中....");
break;
}
//注意这里!
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----running...");
}
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
//在main线程中改t1的标志位
t1.interrupt();
}
}
捕捉到异常catch处理时再调一次interrupt:
public class Interrupt3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("标识位变更为true,有线程请求停止本线程,即将主动停止,保存数据中....");
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
//异常被捕捉后,再次将标志位置为true
Thread.currentThread().interrupt(); //!!!##
e.printStackTrace();
}
System.out.println("----running...");
}
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
//在main线程中改t1的标志位
t1.interrupt();
}
}
为什么要在异常处再调一次interrupt?
- 中断标志位默认为false
- 正常情况下,调用t1.interrrupt,标志位改为true
- 线程调用了sleep/join/wait且处于阻塞状态时,调用t1.interrrupt,抛出InterruptionException,且重置标志位为false,进而导致循环继续,线程未成功中止
- 所以须在catch块中,再次设置中断标志位为true才行
源码中已有说明:
8、静态方法interrupted的注意点
前面提到,静态方法interrupted:
- a、返回当前线程的中断状态
- b、将当前线程的中断状态重新设为false ,类似i++,先返回i,再做自增1
写个Demo验证下
public class Interrupt4 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
System.out.println("-------1");
Thread.currentThread().interrupt(); //中断标志位改为true
System.out.println("-------2");
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
}
}
结果:
看下源码:
interrupted和isInterrupted这两个方法底层调用的都是同一个方法(native),区别是传的值不同,静态方法传了true,即清理了中断标志位,实例方法isInterrupted则不会,因为传了false。