0%

探究 interrupt() 和 park() 的影响

前言

本文产生原因源于 AQS 中的 parkAndCheckInterrupt() 方法。

AQS-parkAndCheckInterrupt

如注释所说,该方法的作用为:挂起线程后检查是否中断,如果被中断则返回 true。

该方法被调用的时机为:获取锁失败,线程在同步队列中挂起自己,等待被唤醒后继续获取锁。

问题:我们只是需要知道线程是否在等待时中断而已,为什么返回值不用 Thread.currentThread().isInterrupted() 方法?

前置知识:

  1. 中断线程可以唤醒被挂起的线程
  2. Thread#isInterrupted() 和 Thread.interrupted() 的区别是:后者会重置中断状态

本文将扩展一下标题所述的范围,通过几个 demo 来探究一下 Thread#interrupt() / Thread.interrupted() / Thread.sleep() / LockSupport.park() / LockSupport.unpark() 之间的一些影响。

测试环境

java-version

注:为了保证结果严谨性,每个 demo 都会包含多线程和单线程的模拟。

case1: interrupt and park

A 线程多次调用 LockSupport.park() 挂起自己,B 线程调用 interrupt() 中断 A 线程 or
A 线程先调用 interrupt() 中断自己,再多次调用 LockSupport.park() 挂起自己

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* A 线程多次调用 LockSupport.park() 挂起自己,B 线程调用 interrupt() 中断 A 线程
*/
public class Test {
public static void main(String[] args) throws Exception {
Test1 t = new Test1();
t.start();
Thread.sleep(1000L);
t.interrupt();
}

static class Test1 extends Thread {
@Override
public void run() {
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park();
LockSupport.park();
LockSupport.park();
System.out.println("final park");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* A 线程先调用 interrupt() 中断自己,再多次调用 LockSupport.park() 挂起自己
*/
public class Test {
public static void main(String[] args) {
Test1 t = new Test1();
t.start();
}

static class Test1 extends Thread {
@Override
public void run() {
Thread.currentThread().interrupt();
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park();
LockSupport.park();
LockSupport.park();
System.out.println("final park");
}
}
}

结果

case1-result

结论

调用 interrupt() 方法中断线程前,线程会正常挂起;中断后,无论调用多少次 LockSupport.park(),线程都不会挂起,而是正常运行结束。

case2: interrupt and park and interrupted

(在 case1 的基础上)
A 线程多次调用 LockSupport.park() 挂起自己后调用 Thread.interrupted() 重置中断状态,最后再调用 LockSupport.park() 挂起自己,B 线程调用 interrupt() 中断 A 线程 or
A 线程先调用 interrupt() 中断自己,再多次调用 LockSupport.park() 挂起自己,然后调用 Thread.interrupted() 重置中断状态,最后再调用 LockSupport.park() 挂起自己

code

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
/**
* A 线程多次调用 LockSupport.park() 挂起自己后调用 Thread.interrupted() 重置中断状态,最后再调用 LockSupport.park() 挂起自己,B 线程调用 interrupt() 中断 A 线程
*/
public class Test {
public static void main(String[] args) throws Exception {
Test2 t = new Test2();
t.start();
Thread.sleep(1000L);
t.interrupt();
}

public static class Test2 extends Thread {
@Override
public void run() {
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park();
System.out.println("after second park");
LockSupport.park();
System.out.println("after third park");
System.out.println(Thread.interrupted()); // true
System.out.println(Thread.interrupted()); // false
LockSupport.park(); // 挂起
System.out.println("final park");
}
}
}
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
/**
* A 线程先调用 interrupt() 中断自己,再多次调用 LockSupport.park() 挂起自己,然后调用 Thread.interrupted() 重置中断状态,最后再调用 LockSupport.park() 挂起自己
*/
public class Test {
public static void main(String[] args) {
Test2 t = new Test2();
t.start();
}

public static class Test2 extends Thread {
@Override
public void run() {
Thread.currentThread().interrupt();
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park();
System.out.println("after second park");
LockSupport.park();
System.out.println("after third park");
System.out.println(Thread.interrupted()); // true
System.out.println(Thread.interrupted()); // false
LockSupport.park(); // 挂起
System.out.println("final park");
}
}
}

结果

case2-result

结论

(结合 case1 的结论)
调用 interrupt() 方法中断线程前,线程会正常挂起;中断后,无论调用多少次 LockSupport.park(),线程都不会挂起,直到重置中断状态后,再次调用 LockSupport.park() 线程才会挂起。

case3 interrupt and park and interrupted 2

(在 case2 的基础上)
将调用 Thread.interrupted() 重置中断状态提前到 interrupt() 中断后,再多次调用 LockSupport.park()。即 Thread.interrupted() 后先立即进行重置中断状态操作。

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Test {
private static boolean flag = true;

public static void main(String[] args) {
Test3 t = new Test3();
t.start();
t.interrupt();
flag = false;
}

public static class Test3 extends Thread {
@Override
public void run() {
while (flag) {}
System.out.println(Thread.interrupted()); // true
System.out.println(Thread.interrupted()); // false
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park(); // 挂起
System.out.println("final park");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test {
public static void main(String[] args) {
Test3 t = new Test3();
t.start();
}

public static class Test3 extends Thread {
@Override
public void run() {
Thread.currentThread().interrupt();
System.out.println(Thread.interrupted()); // true
System.out.println(Thread.interrupted()); // false
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park(); // 挂起
System.out.println("final park");
}
}
}

结果

case3-result

结论

调用 interrupt() 方法中断线程然后立即重置中断状态,之后,第一次调用 LockSupport.park() 不会挂起线程,再次调用 LockSupport.park() 线程才会挂起。

case4: sleep and park

A 线程 sleep 时被 B 线程中断后,再多次调用 LockSupport.park()。

code

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
/**
* A 线程 sleep 时被 B 线程中断后,再多次调用 LockSupport.park()
*/
public class Test {
public static void main(String[] args) {
Test4 t = new Test4();
t.start();
t.interrupt();
}

public static class Test4 extends Thread {
@Override
public void run() {
try {
Thread.sleep(3000L);
System.out.println("sleep end");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("interrupt");
}
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park(); // 挂起
System.out.println("final park");
}
}
}

结果

case4-result

结论

(类似 case3 的结论)
线程 sleep 时被中断抛出 InterruptedException 重置中断状态,之后,第一次调用 LockSupport.park() 不会挂起线程,再次调用 LockSupport.park() 线程才会挂起。

case5: unpark and park

A 线程多次调用 LockSupport.unpark(threadB),B 线程多次调用 LockSupport.park() 挂起自己 or
A 线程多次调用 LockSupport.unpark(threadA),再多次调用 LockSupport.park() 挂起自己

code

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
/**
* A 线程多次调用 LockSupport.unpark(threadB),B 线程多次调用 LockSupport.park() 挂起自己
*/
public class Test {
public static void main(String[] args) {
Test5 t = new Test5();
t.start();
LockSupport.unpark(t);
LockSupport.unpark(t);
LockSupport.unpark(t);
}

public static class Test5 extends Thread {
@Override
public void run() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park(); // 挂起
LockSupport.park();
System.out.println("final park");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* A 线程多次调用 LockSupport.unpark(threadA),再多次调用 LockSupport.park() 挂起自己
*/
public class Test {
public static void main(String[] args) {
Test5 t = new Test5();
t.start();
}

public static class Test5 extends Thread {
@Override
public void run() {
LockSupport.unpark(Thread.currentThread());
LockSupport.unpark(Thread.currentThread());
LockSupport.unpark(Thread.currentThread());
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park(); // 挂起
LockSupport.park();
System.out.println("final park");
}
}
}

结果

case5-result

结论

无论调用多少次 LockSupport.unpark(thread),都只会提供给线程一个许可。

小结

至此,前言中问题的原因就比较清晰了:中断后,如果不清除中断状态,下次 park 是不生效的。

假如该线程下次仍然获取锁失败,再调用 parkAndCheckInterrupt() 就不能再挂起自己了。因此这里需要一个又能重置线程中断状态又能返回线程是否中断的方法,Thread.interrupted() 方法是再合适不过了。


欢迎关注我的其它发布渠道