面试题七:如何停止一个线程?
如何停止一个线程?
一、面试官视角:这道题想考察什么?
- 是否对线程的用法有了解
- 是否对线程的stop方法有了解
- 是否对线程stop过程中存在的问题有认识
- 是否熟悉interrupt中断的用法
- 是否能解释清楚使用boolean标志位的好处
- 是否知道interrupt底层的细节
- 通过该题目能够转移话题到线程安全,并阐述无误
二、题目剖析:
1、void stop()
、void stop(Throwable obj)
,这两个线程停止的方法已经在Jdk1.1版本被废弃了,连同着暂停和继续的方法也被废弃了。
为什么被废弃呢?因为直接停止线程是不安全的。
场景:
Thread1运行,对内存加锁写入中,此时Thread2也想访问这块内存,但它已经拿不到内存锁了,因为Thread1在访问,那么Thread2就会处于阻塞状态,等待内存锁释放,而此时Thread1被暂停了,但它仍持有内存锁,因此Thread2仍然处于阻塞状态,那暂停之后会不会恢复呢,不知道,如果不会恢复的话,那Thread2就得一直等着,这样就会导致一些运行上的问题,万一Thread2还持有了Thread1想要的锁,那就是死锁了。
此时情况又发生了变化,Thread1被直接停止了,那它会立即释放内存锁,根本没有缓冲的时间,直接停止,直接干掉,假如原本在内存中写入数据,写到一半被停止了,而此时Thread2将会获得内存锁,开始等待CPU的时间片,当获取到时间片访问内存时,发现内存状态异常,这是因为Thread1还没有执行完,就被停止了,留下了烂摊子给Thread2,那Thread2估计也得面临crash,正是因为有这种原因,官方就把线程停止的方法给废弃了,连同着暂停和继续的方法也被废弃了。
2、如何设计可以随时被中断而取消的任务线程?
线程在设计过程中,主要是任务执行的设计,线程往往与任务是强绑定的,任务执行完了,线程也就结束了。虽然现在线程不能在执行过程中被直接停止,但任务的执行是可以停止的,所以线程的运行模式,其实是个协作的任务执行模式,我们想让线程结束,其目的是想要任务结束。
因此在设计上,我们应该在任务上添加中断方式:
interrupt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23目标线程:
class InterruptableThread extends Thread {
@Override
public void run() {
try {
sleep(5000);
}catch(InterruptedException e) {
System.out.println("interrupted!");
}
}
}
中断通知:
public static void interruptAThread() {
Thread thread = new InterruptableThread();
thread.start();
try {
Thread.sleep(2000);
}catch(InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52interrupt有的情况也是不支持的!
目标线程:
class UninterruptableThread extends Thread {
public void run() {
super.run();
// 在for循环中,循环很多次的时候,是不支持interrupt的
for(int i=0;i<100000;i++) {
if(i%10000 == 0) {
System.out.println(i);
}
}
}
}
中断通知:
public static void cannotInterruptAThread() {
Thread thread = new UninterruptableThread();
thread.start();
thread.interrupt();
}
想要支持interrupt,可以修改一下目标线程:
class InterruptableThread1 extends Thread {
public void run() {
super.run();
for(int i=0;i<100000;i++) {
// 每次循环都调用一下interrupt(),确认有没有中断通知,如果收到中断通知了,这个时候我们也可以中断,也可以不中断,当然按照中断模式来的话,就停止任务,此时线程也就结束了
// 关于中断状态的读取,有两个方法:
// 1、interrupted(),是静态方法,获取当前线程的中断状态,并清空
// 1.1 当前运行的线程
// 1.2 中断状态调用后清空,重复调用后续返回false【thread.cc代码中:SetInterruptedLocked(false); // 清空当前中断状态】
//
// 2、isInterrupted(),是非静态方法,获取该线程的中断状态,不清空
// 2.1 调用的线程对象对应的线程
// 2.2 可重复调用,中断清空前一直返回true
if(interrupted()) {
break;
}
if(i%10000 == 0) {
System.out.println(i);
}
}
}
}
中断通知:
public static void handleInterruptionThread() {
Thread thread = new InterruptableThread1();
thread.start();
thread.interrupt();
}boolean标志位
其实interrupt代码逻辑底层也使用了boolean标志位,只是底层代码实现时还加了锁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23目标线程:
class InterruptableThread extends Thread {
// boolean isStopped = false; // 此时的isStopped没有加锁,会存在线程间可见性问题,就是在中断通知那里改了true,这里未必能看得到,这是因为Java的内存模型导致的。
// 为了保证线程间可见性,需要加一个volatile,告诉虚拟机,这个字段是易变的,易变的意思就是,哪里改了这个字段的内容,我这里就得看到。
volatile boolean isStopped = false;
public void run() {
super.run();
for(int i=0;i<100000;i++) {
if(isStopped) {
return;
}
}
}
}
中断通知:
public static void booleanFlag() {
InterruptableThread thread = new InterruptableThread();
thread.start();
...
thread.isStopped = true;
...
}interrupt与boolean标志位对比
interrupt boolean 系统方法(sleep) 是 否 使用JNI 是 否 加锁 是 否 触发方式 抛异常 布尔值判断,也可抛异常 - 需要支持系统方法时用中断(功能性)
- 其他情况用boolean标志位(性能)
三、题目结论:
- 为什么线程不应该被直接stop?因为涉及到资源清理的问题,可能会给其它线程留下烂摊子。
- 线程内置中断机制的使用与原理:通过volatile、boolean标志位通知线程停止
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!