在java中有3种停止线程的方法
- 线程正常退出,也就是当前线程的run方法正常执行完毕后线程终止
- 使用stop方法强行终止线程 不过这个方法已经被废弃
- 使用interrupt()方法中断线程
接下来介绍这些方法的优缺点
首先介绍interrupt()方法
如果仅仅在当前线程调用interrupt()方法其实并不会马上停止该线程,只是在当前线程打了一个终止的标记。
可以看以下演示:
// 创建t1线程
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
System.out.println("t1线程:" + i);
}
});
// 执行t1线程
t1.start();
Thread.sleep(20);
// t1调用interrupt()方法
t1.interrupt();
System.out.println("t1调用interrupt()方法");
}
执行结果
我们可以发现:先让主线程休息20ms,t1 线程执行到 i =1796 时 调用了t1.interrupt()。但是之后t1线程并没有停止还在继续执行。
那怎么用interrupt()方法实现停止线程呢? 我们先来看看怎么判断线程已经中断
Thread类中提供了2个方法来判断线程是否中断
可以看到 interrupted() 是静态方法,如果调用Thread.interrupted(),这就表示判断执行该方法的当前线程 是否中断。
而 isInterrupted() 是实例方法,必须通过线程对象来调用,这个方法表示判断调用该方法的线程 是否中断。
// 创建t1线程
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
System.out.println("t1线程:" + i);
}
});
// 执行t1线程
t1.start();
Thread.sleep(20);
// t1调用interrupt()方法
t1.interrupt();
System.out.println("线程是否已经中断:" + t1.interrupted());
System.out.println("线程是否已经中断:" + t1.interrupted());
可以看到打印了2个false,这当然是可以预见的,因为上述代码调用的是静态方法
interrupted() 。这个方法表示正在执行该方法的当前线程是否中断
虽然是用t1线程调用的 但是当前线程是主线程 它可从来没有调用interrupt()方法 所以当然打印的是false。这也告诉我们静态方法尽量用类名调用,不然容易产生误会。
如果我们把上述的判断中断代码改成这样
System.out.println("线程是否已经中断:" + t1.isInterrupted());
System.out.println("线程是否已经中断:" + t1.isInterrupted());
可以看到因为调用了t1.interrupt()方法 所以返回了true 而且是2个
接下来我们看看静态方法interrupted()的正确使用
Thread.currentThread().interrupt();
System.out.println("线程是否已经中断:" + Thread.interrupted());
System.out.println("线程是否已经中断:" + Thread.interrupted());
首先第一个true
是因为我们先使当前线程调用了interrupt()中断方法
然后利用Thread.interrupted()判断当前线程是否中断 理所当然是true
那为什么第二次判断是false呢?
这是因为Thread.interrupted()这个静态方法在第一次调用之后 第二次调用会清除中断状态
而对象.isInterrupted()则不会
总结一下这2个方法
Thread.interrupted():测试当前线程是否已经中断,连续执行会将中断标志清除
对象.isInterrupted():测试该线程对象是否已经中断,连续执行不会将中断标志清除
那怎么利用interrupt() 和interrupted() 结合终止线程呢?
// 创建t1线程
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
if(Thread.interrupted()) {
System.out.println("线程已经终止");
break;
}
System.out.println("t1线程:" + i);
}
System.out.println("当前run方法还可以继续执行");
});
// 执行t1线程
t1.start();
Thread.sleep(20);
// t1调用interrupt()方法
t1.interrupt();
我们只需要调用interrupt()方法 然后在线程中判断是否已经中断 如果中断就在里面进行相关处理
可以看到这里输出的线程终止只是代表for循环里面的代码不再执行
因为我们调用了break方法只是跳出当前循环
如果我们想让以后代码都不再执行 可以直接调用return
// 创建t1线程
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
if(Thread.interrupted()) {
System.out.println("线程已经终止");
return;
}
System.out.println("t1线程:" + i);
}
System.out.println("当前run方法还可以继续执行");
});
// 执行t1线程
t1.start();
Thread.sleep(20);
// t1调用interrupt()方法
t1.interrupt();
不建议用return 我们可以用抛异常然后在catch中进行相关的处理
// 创建t1线程
Thread t1 = new Thread(() -> {
try {
for (int i = 0; i < 5000; i++) {
if(Thread.interrupted()) {
System.out.println("线程已经终止");
throw new InterruptedException("线程终止");
}
System.out.println("t1线程:" + i);
}
System.out.println("当前run方法还可以继续执行");
} catch (InterruptedException e) {
System.out.println("我真的退出了");
e.printStackTrace();
}
});
// 执行t1线程
t1.start();
Thread.sleep(20);
// t1调用interrupt()方法
t1.interrupt();
可以看到线程真的退出了。。。
这里我们还要注意一点
如果线程处于以下情况,那么当线程被中断的时候,能自动感知到
- 来自 Object 类的 wait()、wait(long)、wait(long, int),
- 来自 Thread 类的 join()、join(long)、join(long,
int)、sleep(long)、sleep(long, int)
这几个方法的相同之处是,方法上都有: throws InterruptedException
如果线程阻塞在这些方法上(我们知道,这些方法会让当前线程阻塞),这个时候如果其他线程对这个线程进行了中断,那么这个线程会从这些方法中立即返回,抛出 InterruptedException 异常,同时重置中断状态为 false。
对于以上 3 种情况是最特殊的,因为他们能自动感知到中断(这里说自动,当然也是基于底层实现),并且在做出相应的操作后都会重置中断状态为 false。
那是不是只有以上 3 种方法能自动感知到中断呢?不是的,如果线程阻塞在 LockSupport.park(Object obj) 方法,也叫挂起,这个时候的中断也会导致线程唤醒,但是唤醒后不会重置中断状态,所以唤醒后去检测中断状态将是 true。
在并发包中,有非常多的处理中断的例子,提供两个方法,分别为响应中断和不响应中断,对于不响应中断的方法,记录中断而不是丢失这个信息。如 Condition 中的两个方法就是这样的:
void await() throws InterruptedException;
void awaitUninterruptibly();
除了用interrupt() 我们还可以设置一个flag变量 注意必须用volatile关键字修饰 保证其可见性
public class StopThread {
private static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Person person = new Person();
// 创建t1线程
Thread t1 = new Thread(() -> {
while (flag) {
try {
Thread.sleep(500);
System.out.println("我正在运行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("我已经停止了");
});
// 执行t1线程
t1.start();
Thread.sleep(3000);
System.out.println("主线程说:哈哈你马上就停止了");
flag = false;
}
}
使用stop强行停止线程
这个方法十分暴力 如果当前线程正在执行同步方法 那么stop方法会强行中断线程并 释放锁,这就可能导致数据的不一致性 并且在用stop方法终结线程 不能保证线程资源的正常释放
可以看以下代码
@Data
class Person {
private String name = "wyy";
private int age = 200;
}
Person person = new Person();
// 创建t1线程
Thread t1 = new Thread(() -> {
person.setName("wmh");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
person.setAge(20);
});
// 执行t1线程
t1.start();
Thread.sleep(100);
t1.stop();
System.out.println(person);
}
所以强烈不建议用stop方法,他已经废弃
使用suspend 和 resume
这2个方法可以实现对线程的暂停和恢复 但和stop一样也被废弃
下面是对susspend resume stop 的演示
public static void main(String[] args) throws InterruptedException {
Person person = new Person();
// 创建t1线程
Thread t1 = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我在运行:" + new Date());
}
});
// 执行t1线程
t1.start();
Thread.sleep(3000);
t1.suspend();
System.out.println("主线程在运行:" + new Date());
Thread.sleep(3000);
t1.resume();
Thread.sleep(3000);
t1.stop();
}
结果如下
suspend和resume为什么会被抛弃呢?
因为suspend调用后 线程不会释放已经占用的资源(锁),而是占着资源进入睡眠状态,这样容易引发死锁。
关于暂停恢复操作 可以用等待/通知实现
参考:java多线程编程核心技术
https://www.javadoop.com/post/AbstractQueuedSynchronizer-2