何为Java线程中断


何为Java线程中断

Java线程中断是一个大多熟人不太关心的知识点,有的人可能一辈子也用不到,这是一位网络上的大牛说的。

Java线程的中断其实非常简单,说白了就是一个中断标志位的改变。先来看Thread类3个关于中断的方法:

  • interrupt():

    实例方法,中断该线程;意思就是把中断标志位改为true。

  • isInterrupted()

    实例方法,测试该线程是否被中断;显然,根据中断标志位返回true或false。

  • Thread.interrupted()

    静态方法,测试当前线程是否中断;该方法与isInterrupted()类似,只不过,如果线程发生了中断,则把中断标志清除,也就是改为false。

与中断相关的自然就是中断异常InterruptedException,当线程执行某些方法时不希望被中断,若被中断则抛出中断异常,如Thread.sleep()方法、Object#wait()方法。

中断线程sleep休眠

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
public class ThreadInterruptedTest {

public static String currentTime() {

return new SimpleDateFormat("HH:mm:ss").format(new Date());
}

public static void sleep(int second) {

try {
Thread.sleep(second * 1000L);
} catch (InterruptedException e) {
System.out.println("sleep中断异常");
}
}

public static void main(String[] args) {

// 创建子线程 t 并启动
Thread t = new Thread(() -> {
System.out.println("线程start - " + currentTime());
sleep(10);
System.out.println("线程end - " + currentTime());
});
t.start();

// 主线程在2s后中断子线程 t
sleep(2);
t.interrupt();
}
}

执行结果

1
2
3
线程start - 16:13:53
sleep中断异常
线程end - 16:13:55

中断线程因调用Object#wait()方法进入等待的状态

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
public class ThreadInterruptedTest {

public static String currentTime() {

return new SimpleDateFormat("HH:mm:ss").format(new Date());
}

static final Object COMMON = new Object();
public static void waitOfCommon() {

synchronized (COMMON) {

try {
COMMON.wait();
} catch (InterruptedException e) {
System.out.println("等待COMMON被中断");
}
}
}

public static void main(String[] args) {

// 创建子线程 t 并启动
Thread t = new Thread(() -> {
System.out.println("线程start - " + currentTime());
waitOfCommon();
System.out.println("线程end - " + currentTime());
});
t.start();

// 主线程在2s后中断子线程 t
sleep(2);
t.interrupt();
}
}

执行结果

1
2
3
线程start - 16:20:43
等待COMMON被中断
线程end - 16:20:45

如果不执行中断线程t,由于COMMON没有其它线程调用notify()方法或notifyAll()方法,线程 t 永远不会醒来。

我们知道,线程调用Object#wait()方法时,会释放该对象的锁,当其他线程拿到该对象的锁时,是否可以中断调用Object#wait()方法的线程?

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
52
53
54
55
56
57
58
59
public class ThreadInterruptedTest {

public static String currentTime() {

return new SimpleDateFormat("HH:mm:ss").format(new Date());
}

public static void sleep(int second) {

try {
Thread.sleep(second * 1000L);
} catch (InterruptedException e) {
System.out.println("sleep中断异常");
}
}

static final Object COMMON = new Object();
public static void main(String[] args) {

// 创建子线程 t1 并启动
Thread t1 = new Thread(() -> {

synchronized (COMMON) {
System.out.println("线程1 start - " + currentTime());
try {
COMMON.wait();
} catch (InterruptedException e) {
System.out.println("线程1 等待COMMON被中断");
}
System.out.println("线程1 end - " + currentTime());
}
});
t1.start();

sleep(2);// 主线程休眠2s确保线程1 进入等待状态

Thread t2 = new Thread(() -> {

synchronized (COMMON) {

System.out.println("线程2 start - " + currentTime());
// 每过一秒打印数字,打印6次
for (int i = 0; i < 6; i++) {
System.out.println("println " + i);
sleep(1);
}
COMMON.notifyAll(); // 最后唤醒其它线程
System.out.println("线程2 end - " + currentTime());
}

});
t2.start();

// 主线程在2s后中断子线程 t1
sleep(2);
t1.interrupt();
System.out.println("main end");
}
}

执行结果

1
2
3
4
5
6
7
8
9
10
11
12
线程1 start - 16:56:10
线程2 start - 16:56:12
println 0
println 1
main end
println 2
println 3
println 4
println 5
线程2 end - 16:56:18
线程1 等待COMMON被中断
线程1 end - 16:56:18

可以看出,线程t 在等待状态时,虽然被中断,但此时 COMMON 对象的锁被线程2 持有,所以没有响应中断,当线程2 调用COMMON.notifyAll()方法,线程 1获得 COMMON 对象的锁,发现自己被中断了。

推测线程在调用Object#wait()方法时,并没有立即让出锁,此时锁还在自己手上,但允许其它线程过来抢,直到有其它线程拿走锁后,才真正的失去锁。

所以对于Object#wait()方法,线程只有在获得监视对象的锁时才能响应该方法的中断。

中断线程因调用LockSupport.park()方法进入等待的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 public static void main(String[] args) {
// 创建子线程 t 并启动
Thread t = new Thread(() -> {

System.out.println("线程 start - " + currentTime());
LockSupport.park();
System.out.println("线程 end - " + currentTime());
});
t.start();

// 主线程在2s后中断子线程 t
sleep(2);
t.interrupt();
System.out.println("main end");
}

执行结果

1
2
3
线程1 start - 17:16:23
main end
线程1 end - 17:16:27

当线程调用LockSupport.park()使cpu对其不再进行调度时,可以通过中断的方式唤醒(当然LockSupport.unpark(Thread)也可以),继续执行该线程。

小结

说了这么多,一般开发中线程中断用到的场景少之又少,更多的是出现在线程调度相关的操作中,如AQS,都是大佬们玩的。

似乎线程中断的设计就是为线程调度而设计的。开发中就算强行为了用而用,用其作为线程通信的手段,也会有更好的方案代替。