0%

Java并发编程基础

一、线程基本含义

1.1、Java线程与操作系统线程

Java线程由Java语言模型提供并确保在不同操作系统环境上的语义一致性,JDK负责具体实现和屏蔽操作系统环境差异,比如在现代Linux操作系统中,Java线程会被映射到Linux原生线程。

1.2、线程对应的数据结构和属性

进程和线程具有承载其数据的相应数据结构实体:

  • Linux进程如是,由Linux内核提供
  • Linux线程如是,由Linux内核提供
  • Java进程如是,由JDK提供,比如“对应Unix操作系统环境的类java.lang.UNIXProcess”
  • Java线程如是,由JDK提供,具体且唯一(即不同操作系统环境对应同一个类)的类java.lang.Thread

接下来介绍Java线程常见的几个属性(在相应的java.lang.Thread实例对象中设置),具体有:线程组,线程优先级,是否为后台线程,线程名字和target。

1.2.1、线程组

通过线程组同时管理一批线程,所有线程默认属于“默认线程组”。
在实际使用场景中,几乎不被使用。

1.2.2、线程优先级

线程优先级越高,被分配到CPU时间片的概率越大。

线程可设置优先级,线程调度器倾向于优先调用优先级高的线程。但仅是“倾向于”,不可依赖。
在实际使用场景中,几乎不被使用。因为:

  • 对于Java线程的线程优先级,JDK不确保在不同操作系统环境的语义一致性,因此:1)线程优先级设定在有些操作系统中可能完全无效;2)在线程优先级设定有效的前提下,相同优先级设定,在A操作系统和B操作系统的展现行为可能不同
  • 在线程调度中,线程优先级只是一个维度的因素,还有其他维度的因素,因此,只能说“线程优先级越高,被分配到CPU时间片的概率越大”,而不是必然的

1.2.3、是否为后台线程

基于“是否为后台线程”的角度,Java线程分为:后台线程和非后台线程。
关于两者的描述有以下几点:

  • 常见的是非后台线程;后台线程不是Java进程中不可或缺的线程,一般只用来提供通用服务,比如“Finalizer线程”
  • 非后台线程创建的线程默认为非后台线程;后台线程创建的线程默认为后台线程。或者可以且必须在线程启动之前,通过方法setDaemon(boolean on)手动设置是否为后台线程
  • 当Java进程中不存在非后台线程,进程立即退出(会杀死所有后台线程),效果等价于执行System.exit(int status)方法。这里有一个引申点:我们知道“try-catch-finally”语句结构中的“finally”语句一般都会执行完成,但是当由于执行System.exit(int status)方法立即退出Java进程时,“finally”语句并不保证会被执行,现在新增一种“finally”语句并不保证会被执行的情形,即“不存在非后台线程,Java进程立即退出”,当然此时不保证会被执行的“finally”语句必在后台线程中

1.2.4、线程名字

线程具有默认名字,但是为了后续排查时的可读性,建议取一个可读性好的名字

1.2.5、target

调用Thread实例的start()方法,开启一个Java线程,最终会去执行该Thread实例的run()方法,其源代码如下,其中的target是一个Runnable实例对象,其run()方法包含具体的工作逻辑。

Thread类的run()方法源代码:

1
2
3
4
5
6
@Override
public void run() {
if (target != null) {
target.run();
}
}

二、线程常见动作方法

线程常见动作方法(java.lang.Thread类中方法)如下:

  • suspend方法:已废弃
  • resume方法:已废弃
  • stop方法:已废弃
  • yield方法:执行yield方法,表明当前线程愿意放弃剩下的时间片,“建议”线程调度器可以调度运行其他的线程。但仅是“建议”,不可依赖
  • start方法:构造完Thread实例后,调用其上的start()方法,线程状态从NEW转到RUNNABLE,详见“三、线程状态与转移”小节。需要注意的是,在线程A中创建线程B对应的“java.lang.Thread”类实例,然后调用其上的start()方法,它会立即返回,并不会阻塞线程A的运行,此后线程B和线程A处于平等的地位由线程调度器统一调度
  • join方法:在线程A中调用执行线程B对应的Thread实例上的join方法,表示挂起线程A直到线程B执行完成转到TERMINATED状态,详见“三、线程状态与转移”小节
  • interrupt方法:中断线程,详见“四、中断”小节

三、线程状态与转移

Java线程的状态转移示意图如图1所示。

图1

3.1、Java线程状态

Java线程状态如表1所示。

表1[1]

状态名称 说明
NEW 初始状态,“java.lang.Thread”实例被构建,但是还未调用其上start()方法
RUNNABLE 运行状态,实际上包括“RUNNING(运行)”,“READY(等待CPU)”和“IO_WAIT(等待IO)”3种情形
BLOCKED 阻塞状态,表示线程申请synchronized锁而不得
WAITING 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或者中断)才能退出等待状态
TIMED_WAITING 超时等待状态,退出条件在WAITING状态的基础之上加了“到达指定的超时时间也可退出等待状态”
TERMINATED 终止状态,表示当前线程已经执行完毕

几点说明

  • 参见java.lang.Thread类中State枚举类
  • Java线程状态由Java语言自定义,与Linux线程的状态(Linux线程的状态跟Linux进程的状态一致,参见[2])非一一映射,比如“Java线程的RUNNABLE对应Linux线程的RS”,“Java线程的BLOCKEDWAITINGTIMED_WAITING都对应Linux线程的S”。实验代码1证明当Java线程处于“IO_WAIT(等待IO)”情形时,其Java线程状态为RUNNABLE,对应的Linux线程状态为S
  • 挂起虚状态包括BLOCKEDWAITINGTIMED_WAITING
  • Java线程的存活期意指:从调用其对应的Thread实例的start()方法开始,到其执行完成进入TERMINATED状态之前。在Java线程存活期内,调用其对应Thread实例的isAlive()方法,返回true,否则返回false,实验代码2是一个实验验证。本文的很多讨论都建立在“Java线程处于存活期”的前提下,否则失去实际意义,比如“讨论Thread.interrupt()Thread.isInterrupted()Thread.interrupted()LockSupport.unpark(Thread thread)这4个方法的调用,只有目标Java线程存活才有实际意义”

实验代码1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.InputStream;

public class Main {
public static void main(String[] args) {

InputStream in = System.in;

try {
int b = 0;
while ((b = in.read()) != -1) {
System.out.println(b);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

通过ps命令知上述Java进程的ID为18141,通过jstack 18141命令打印线程栈日志,并在其内找到对应的main线程记录:

1
2
3
4
5
6
7
8
"main" #1 prio=5 os_prio=0 tid=0x00007fe0b000a800 nid=0x46de runnable [0x00007fe0b6d86000]
java.lang.Thread.State: RUNNABLE
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:255)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
- locked <0x000000078661b698> (a java.io.BufferedInputStream)
at Main.main(Main.java:10)

根据nid字段值“0x46de”知道对应的Linux线程ID为18142,再使用view /proc/18141/task/18142/status命令查看该Linux线程的状态[4],部分结果如下,可知该Linux线程的状态为S

1
2
3
4
5
6
Name:   java
Umask: 0002
State: S (sleeping)
Tgid: 18141
Ngid: 0
Pid: 18142

实验代码2

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
public class Main {
public static void main(String[] args) throws InterruptedException {

Thread a = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("finish");

try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "test");

a.start();

System.out.println(a.isAlive());

Thread.sleep(10000L);

System.out.println(a.isAlive());
}
}

程序运行结果如下:

1
2
3
true
finish
false

3.2、挂起 <-> RUNNABLE的转移条件

3.2.1、Thread.sleep方法

有两个重载方法:sleep(long millis)sleep(long millis, int nanos),两者的区别只在于等待时间的设定精度。
线程A中执行sleep方法,线程A进入TIMED_WAITING状态,等待设定的时间到期后返回RUNNABLE状态。

实验代码3

1
2
3
4
5
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(100000L);
}
}

在100s睡眠时间到达前打印线程栈,线程栈中main线程的记录如下:

1
2
3
4
"main" #1 prio=5 os_prio=0 tid=0x00007f574c00a800 nid=0x1f36 waiting on condition [0x00007f5752474000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at Main.main(Main.java:3)

3.2.2、synchronized方法/语句块

线程A在执行synchronized方法/语句块之前,需要申请相应的synchronized锁,如果申请不到,线程A被放入锁对象关联的cxq或者EntryList队列,并进入BLOCKED状态,待后续申请到synchronized锁时,从cxq或者EntryList队列移除,再返回到RUNNABLE状态。

实验代码4

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
public class Main {
public static void main(String[] args) throws InterruptedException {

Object obj = new Object();

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
Thread.sleep(100000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "threadA");

threadA.start();

// 让threadA去申请到obj对象对应的synchronized锁
Thread.sleep(5000L);

Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}
}, "threadB");

threadB.start();
}
}

在Java线程threadA睡眠时间100s到达前,线程栈中threadA和threadB线程的记录如下:

1
2
3
4
5
6
7
8
9
10
11
12
"threadB" #10 prio=5 os_prio=0 tid=0x00007f9c440ef800 nid=0x2133 waiting for monitor entry [0x00007f9c2c67f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Main$2.run(Main.java:31)
- waiting to lock <0x000000078665b0c0> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)

"threadA" #9 prio=5 os_prio=0 tid=0x00007f9c440ee000 nid=0x212f waiting on condition [0x00007f9c2c780000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at Main$1.run(Main.java:13)
- locked <0x000000078665b0c0> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)

3.2.3、Object类的wait/notify/notifyAll实例方法

方法含义:

  • wait方法
    • 在调用对象obj的wait方法之前,必先获得对象obj对应的synchronized锁,否则会抛出IllegalMonitorStateException异常,然后在调用wait方法之后会首先释放掉已获得的obj对应synchronized锁(需要注意的是,不会释放其他已获得的synchronized锁
    • 线程A调用执行对象obj的wait方法进入WAITING或者TIMED_WAITING状态,线程A加入到obj对象关联的WaitSet队列,待后续尝试唤醒时,线程A移入obj对象关联的cxq或者EntryList队列,即“等待再次申请获取相应的synchronized锁”,此时A处于BLOCKED状态,待最终获取到相应的synchronized锁,线程A从cxq队列或者EntryList队列移除,线程A被成功唤醒,进入RUNNABLE状态
  • notify方法
    • 在调用对象obj的notify方法之前,必先获得对象obj对应的synchronized锁,否则会抛出IllegalMonitorStateException异常,跟wait方法不同,调用notify方法之后不会释放掉已获得的obj对应synchronized锁,而是需要等到相应的synchronized方法/语句块执行完成退出
    • 线程B调用执行对象obj的notify方法随机选中obj对象关联的WaitSet队列中的一个线程C尝试唤醒,即将其移入obj对象关联的cxq或者EntryList队列
  • notifyAll方法
    • 在调用对象obj的notifyAll方法之前,必先获得对象obj对应的synchronized锁,否则会抛出IllegalMonitorStateException异常,跟wait方法不同,调用notifyAll方法之后不会释放掉已获得的obj对应synchronized锁,而是需要等到相应的synchronized方法/语句块执行完成退出
    • 线程B调用执行对象obj的notifyAll方法尝试唤醒obj对象关联的WaitSet队列中的所有线程,即将他们所有都移入obj对象关联的cxq或者EntryList队列

备注

  • 对于notify/notifyAll方法和wait方法之间的调用顺序关系,默认是“后序”,即“notify/notifyAll方法在wait方法之后调用”,经过实验可发现,如果是“先序”,则notify/notifyAll方法调用发出的唤醒信号直接丢失。接下来在本文中称这类唤醒信号为“一次性唤醒信号

wait方法有3个重载方法:wait()wait(long timeout)wait(long timeout, int nanos)。调用wait()方法进入WAITING状态;调用wait(long timeout)wait(long timeout, int nanos)方法进入TIMED_WAITING状态。

3.2.3.1、wait()方法

线程A调用执行对象obj的wait()方法进入WAITING状态。
唤醒条件:

  • 线程B调用执行对象obj的notify()方法
  • 线程B调用执行对象obj的notifyAll()方法
  • 虚假唤醒。无任何理由直接自唤醒退出,虽然很少发生(JavaDoc的原话是A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup.

成功唤醒:

  • 再次强调下,假定线程A调用对象obj的wait()方法进入WAITING状态,后续发生上述任一唤醒条件,且线程A被选中尝试唤醒,只有当线程A再次申请到obj对应synchronized锁时才被成功唤醒

实验代码5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main {
public static void main(String[] args) throws InterruptedException {

Object obj = new Object();

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("hello world");
}
}
}, "threadA");

threadA.start();
}
}

线程栈中threadA线程的记录如下:

1
2
3
4
5
6
7
8
"threadA" #9 prio=5 os_prio=0 tid=0x00007f62e00f6000 nid=0x29f8 in Object.wait() [0x00007f62ca2a7000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000078665afb8> (a java.lang.Object)
at java.lang.Object.wait(Object.java:502)
at Main$1.run(Main.java:11)
- locked <0x000000078665afb8> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)

程序运行结果:threadA线程挂起不被唤醒。

实验代码6

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
public class Main {
public static void main(String[] args) throws InterruptedException {

Object obj = new Object();

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("hello world");
}
}
}, "threadA");

threadA.start();

Thread.sleep(5000);

Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
obj.notify();

try {
Thread.sleep(60000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "threadB");
threadB.start();
}
}

程序运行结果:在threadB开始运行大约60秒后打印“hello world”,因为threadA被成功唤醒既需要调用notify()方法发出的唤醒信号,也需要threadB释放掉obj对应的synchronized锁。

在threadB执行完obj.notify()语句睡眠60s期间,查看线程栈状态,可发现此时threadA的线程状态为BLOCKED

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"threadB" #10 prio=5 os_prio=0 tid=0x00007f55400ff800 nid=0x592e waiting on condition [0x00007f5529398000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at Main$2.run(Main.java:32)
- locked <0x000000078665b028> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)

"threadA" #9 prio=5 os_prio=0 tid=0x00007f55400fd800 nid=0x592a in Object.wait() [0x00007f5529499000]
java.lang.Thread.State: BLOCKED (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000078665b028> (a java.lang.Object)
at java.lang.Object.wait(Object.java:502)
at Main$1.run(Main.java:11)
- locked <0x000000078665b028> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)

实验代码7

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
public class Main {
public static void main(String[] args) throws InterruptedException {

Object obj = new Object();

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("hello world");
}
}
}, "threadA");

threadA.start();

Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("world hello");
}
}
}, "threadB");

threadB.start();

Thread.sleep(5000);

Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
obj.notifyAll();

try {
Thread.sleep(60000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "threadC");
threadC.start();
}
}

程序运行结果:在threadC开始运行大约60秒后打印如下内容,因为threadA和threadB被成功唤醒既需要调用notifyAll()方法发出的唤醒信号,也需要threadC释放掉obj对应的synchronized锁。

1
2
hello world
world hello

实验代码8

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
public class Main {
public static void main(String[] args) throws InterruptedException {

Object obj = new Object();

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("second");
}
}
}, "threadA");

threadA.start();

// 先让threadA获取synchronized锁,执行wait()方法,释放掉synchronized锁
Thread.sleep(5000);

Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
Thread.sleep(60000);
System.out.println("first");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "threadB");

threadB.start();

Thread.sleep(5000);

Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
obj.notify();
}
}
}, "threadC");
threadC.start();
}
}

程序运行结果如下,因为只有当threadB执行完成释放掉obj对应的synchronized锁,threadA获得上述synchronized锁后,threadA才能被成功唤醒:

1
2
first
second
3.2.3.2、wait(long timeout)wait(long timeout, int nanos)方法

线程A调用执行对象obj的wait(long timeout)wait(long timeout, int nanos)方法进入TIMED_WAITING状态。需要注意的是,线程A调用执行对象obj的wait(0)wait(0,0)方法进入的是WAITING状态。
唤醒条件:

  • 线程B调用执行对象obj的notify()方法
  • 线程B调用执行对象obj的notifyAll()方法
  • 设定的超时时间到期
  • 虚假唤醒。无任何理由直接自唤醒退出,虽然很少发生(JavaDoc的原话是A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup.

成功唤醒:

  • 再次强调下,假定线程A调用对象obj的wait(long timeout)wait(long timeout, int nanos)方法进入TIMED_WAITING状态,后续发生上述任一唤醒条件,且线程A被选中尝试唤醒,只有当线程A再次申请到obj对应synchronized锁时才被成功唤醒

实验代码9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main {
public static void main(String[] args) throws InterruptedException {

Object obj = new Object();

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
obj.wait(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("hello world");
}
}
}, "threadA");

threadA.start();
}
}

在60s时间挂起时间到达前打印线程栈,线程栈中threadA线程的记录如下:

1
2
3
4
5
6
7
"threadA" #9 prio=5 os_prio=0 tid=0x00007fb2340f6000 nid=0x2cb5 in Object.wait() [0x00007fb21e9ae000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000078665afb8> (a java.lang.Object)
at Main$1.run(Main.java:11)
- locked <0x000000078665afb8> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)

程序运行结果:大约60秒后打印“hello world”,因为60秒后设定的超时时间到期尝试唤醒,在获得obj对应的synchronized锁后被成功唤醒。

3.2.4、Thread类的join实例方法

join方法有3个重载方法:join()join(long millis)join(long millis, int nanos),后两者的区别只在于等待时间的设定精度。
现有线程B,其对应的Thread实例对象引用为threadB,在线程A中执行threadB.join()/threadB.join(long millis)/join(long millis, int nanos)方法,其含义是:线程A进入WAITING/TIMED_WAITING状态,直到线程B执行完成进入TERMINATED状态或者设定的超时时间到期。
查看如下join()方法核心实现,可知其核心逻辑通过调用wait(long timeout)方法实现,当线程B执行完成进入TERMINATED状态后,会触发调用下threadB.notifyAll()方法([1]中P190页对此有作阐明):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}

if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

实验代码10

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
import java.util.Date;

public class Main {
public static void main(String[] args) throws InterruptedException {

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "threadA");

threadA.start();

Thread.sleep(5000L);

Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(new Date() + ":hello");
try {
threadA.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date() + ":world");
}
}, "threadB");

threadB.start();

Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(new Date() + ":1");
try {
threadA.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date() + ":2");
}
}, "threadC");

threadC.start();
}
}

在Java线程threadA睡眠时间100s到达前打印线程栈,线程栈信息如下所示:

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
"threadC" #11 prio=5 os_prio=0 tid=0x00007f79cc0f9800 nid=0x42e4 in Object.wait() [0x00007f7996c1a000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000078665b1e8> (a java.lang.Thread)
at java.lang.Thread.join(Thread.java:1252)
- locked <0x000000078665b1e8> (a java.lang.Thread)
at java.lang.Thread.join(Thread.java:1326)
at Main$3.run(Main.java:40)
at java.lang.Thread.run(Thread.java:748)

"threadB" #10 prio=5 os_prio=0 tid=0x00007f79cc0f7800 nid=0x42e3 in Object.wait() [0x00007f7996d1b000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000078665b1e8> (a java.lang.Thread)
at java.lang.Thread.join(Thread.java:1252)
- locked <0x000000078665b1e8> (a java.lang.Thread)
at java.lang.Thread.join(Thread.java:1326)
at Main$2.run(Main.java:25)
at java.lang.Thread.run(Thread.java:748)

"threadA" #9 prio=5 os_prio=0 tid=0x00007f79cc0f5800 nid=0x42e2 waiting on condition [0x00007f7996e1c000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at Main$1.run(Main.java:8)
at java.lang.Thread.run(Thread.java:748)

程序运行结果如下:

1
2
3
4
Thu Dec 17 17:13:53 CST 2020:1
Thu Dec 17 17:13:53 CST 2020:hello
Thu Dec 17 17:15:28 CST 2020:2
Thu Dec 17 17:15:28 CST 2020:world

实验代码11

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
import java.util.Date;

public class Main {
public static void main(String[] args) throws InterruptedException {

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "threadA");

threadA.start();

Thread.sleep(5000L);

Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(new Date() + ":hello");
try {
threadA.join(200000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date() + ":world");
}
}, "threadB");

threadB.start();

Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(new Date() + ":1");
try {
threadA.join(200000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date() + ":2");
}
}, "threadC");

threadC.start();
}
}

在Java线程threadA睡眠时间100s到达前(此时threadB和threadC的睡眠时间更加没有可能到达)打印线程栈,线程栈信息如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"threadC" #11 prio=5 os_prio=0 tid=0x00007f11fc0e9800 nid=0x60c2 in Object.wait() [0x00007f11e5da2000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000078665b1e8> (a java.lang.Thread)
at java.lang.Thread.join(Thread.java:1260)
- locked <0x000000078665b1e8> (a java.lang.Thread)
at Main$3.run(Main.java:41)
at java.lang.Thread.run(Thread.java:748)

"threadB" #10 prio=5 os_prio=0 tid=0x00007f11fc0e8000 nid=0x60c1 in Object.wait() [0x00007f11e5ea3000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000078665b1e8> (a java.lang.Thread)
at java.lang.Thread.join(Thread.java:1260)
- locked <0x000000078665b1e8> (a java.lang.Thread)
at Main$2.run(Main.java:26)
at java.lang.Thread.run(Thread.java:748)

"threadA" #9 prio=5 os_prio=0 tid=0x00007f11fc0e6000 nid=0x60b1 waiting on condition [0x00007f11e5fa4000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at Main$1.run(Main.java:10)
at java.lang.Thread.run(Thread.java:748)

程序运行结果如下:

1
2
3
4
Sat Jan 02 20:35:20 CST 2021:hello
Sat Jan 02 20:35:20 CST 2021:1
Sat Jan 02 20:36:55 CST 2021:2
Sat Jan 02 20:36:55 CST 2021:world

有一个疑惑点,既然核心逻辑是通过调用wait方法实现,那么在调用threadB.join方法后,是否可以不等待threadB执行完成,而是人为手动执行threadB.notify()/notifyAll()方法从而唤醒线程A呢?答案既是肯定的也是否定的,因为在人为唤醒线程A后会由于while(isAlive())语句而再次进入WAITING或者TIMED_WAITING状态,参见实验代码12。

实验代码12

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
60
61
62
63
64
65
66
import java.util.Date;

public class Main {
public static void main(String[] args) throws InterruptedException {

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "threadA");

threadA.start();

Thread.sleep(5000L);

Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(new Date() + ":hello");
try {
threadA.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date() + ":world");
}
}, "threadB");

threadB.start();

Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(new Date() + ":1");
try {
threadA.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date() + ":2");
}
}, "threadC");

threadC.start();

Thread.sleep(5000L);

Thread threadD = new Thread(new Runnable() {
@Override
public void run() {
synchronized (threadA) {
System.out.println(new Date() + ":notifyAll the wait on threadA");
threadA.notifyAll();
System.out.println(new Date() + ":notifyAll the wait on threadA finished");
}
}
}, "threadD");

threadD.start();
}
}

程序运行结果如下:

1
2
3
4
5
6
7
Thu Dec 17 17:20:50 CST 2020:1
Thu Dec 17 17:20:50 CST 2020:hello
Thu Dec 17 17:20:55 CST 2020:notifyAll the wait on threadA
Thu Dec 17 17:20:55 CST 2020:notifyAll the wait on threadA finished
//这里需要等待threadA的100s睡眠时间到达
Thu Dec 17 17:22:25 CST 2020:2
Thu Dec 17 17:22:25 CST 2020:world

3.2.5、LockSupport类的park()/parkNanos(long nanos)/parkUtil(long deadline)/unpark(Thread thread)类方法

方法含义:

  • park()方法。线程A执行park()方法进入WAITING状态,待后续被成功唤醒,进入RUNNABLE状态
  • parkNanos(long nanos)方法。线程A执行parkNanos(long nanos)方法进入TIMED_WAITING状态,待后续被成功唤醒,进入RUNNABLE状态
  • parkUtil(long deadline)方法。线程A执行parkUtil(long deadline)方法进入TIMED_WAITING状态,待后续被成功唤醒,进入RUNNABLE状态
  • unpark(Thread thread)方法。线程B执行unpark(Thread thread)方法唤醒Java线程实例thread对应的线程

备注

  • 本文对park()parkNanos(long nanos)parkUtil(long deadline)这3个方法的相应重载方法park(Object blocker)parkNanos(Object blocker, long nanos)parkUntil(Object blocker, long deadline)不作讨论,因为核心语义一致
  • 对于unpark(Thread thread)方法和park()/parkNanos(long nanos)/parkUtil(long deadline)方法之间的调用顺序关系,默认是“后序”,即“unpark(Thread thread)方法在park()/parkNanos(long nanos)/parkUtil(long deadline)方法之后调用”,经过实验可发现,如果是“先序”,unpark(Thread thread)方法调用发出的唤醒信号并不会直接丢失(可与notify/notifyAll方法调用发出的唤醒信号进行对比)。根据unpark(Thread thread)方法的实现原理,可知调用其发出的唤醒信号的特点是:设置一个0-1标志位,如果是“后序”调用,则经历“设置1标志位 -> 唤醒先前的相应的park/parkNanos/parkUtil方法调用 -> 复位0标志位”过程;如果是“先序”调用,则经历“设置1标志位 -> 唤醒后续的相应的park/parkNanos/parkUtil方法调用 -> 复位0标志位”过程。需要注意的是,由于是一个0-1标志位,故在“先序”调用情形中,提前多次unpark(Thread thread)方法调用与提前1次unpark(Thread thread)方法调用等价。接下来在本文中称这类唤醒信号为“标志位唤醒信号
3.2.5.1、park()方法

线程A执行park()方法进入WAITING状态。
唤醒条件:

  • 线程B调用执行unpark(Thread thread)方法,thread代表线程A对应的Thread实例
  • 虚假唤醒。无任何理由直接自唤醒退出,虽然很少发生(JavaDoc的原话是The call spuriously (that is, for no reason) returns

实验代码13

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.concurrent.locks.LockSupport;

public class Main {
public static void main(String[] args) throws InterruptedException {

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
LockSupport.park();

System.out.println("hello world");
}
}, "threadA");

threadA.start();
}
}

程序运行结果:threadA线程挂起不被唤醒。

线程栈如下所示:

1
2
3
4
5
6
"threadA" #9 prio=5 os_prio=0 tid=0x00007f14400f6000 nid=0x4aa8 waiting on condition [0x00007f1429499000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
at Main$1.run(Main.java:9)
at java.lang.Thread.run(Thread.java:748)

实验代码14

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.concurrent.locks.LockSupport;

public class Main {
public static void main(String[] args) throws InterruptedException {

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
LockSupport.park();

System.out.println("hello world");
}
}, "threadA");

threadA.start();

Thread.sleep(5000);

LockSupport.unpark(threadA);
}
}

程序运行结果如下:

1
hello world

实验代码15

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
import java.util.concurrent.locks.LockSupport;

public class Main {
public static void main(String[] args) throws InterruptedException {

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("hello");

LockSupport.park();

System.out.println("world");

LockSupport.park();
System.out.println("again");
}
}, "threadA");

threadA.start();

LockSupport.unpark(threadA);

// 由于是一个0-1标志位,在“先序”调用情形中,提前多次unpark方法调用与提前1次unpark方法调用等价
LockSupport.unpark(threadA);
}
}

程序运行结果如下:

1
2
3
hello
world
//挂起不被唤醒
3.2.5.2、parkNanos(long nanos)parkUntil(long deadline)

线程A执行parkNanos(long nanos)parkUntil(long deadline)方法进入TIMED_WAITING状态。
唤醒条件:

  • 线程B调用执行unpark(Thread thread)方法,thread代表线程A对应的Thread实例
  • 设定的超时时间到期
  • 虚假唤醒。无任何理由直接自唤醒退出,虽然很少发生(JavaDoc的原话是The call spuriously (that is, for no reason) returns

实验代码16

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.concurrent.locks.LockSupport;

public class Main {
public static void main(String[] args) throws InterruptedException {

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
LockSupport.parkNanos(10L * 1000000000);

System.out.println("hello world");
}
}, "threadA");

threadA.start();
}
}

程序运行结果如下:

1
hello world

在Java线程threadA的挂起时间10s到达前打印线程栈,线程栈记录如下:

1
2
3
4
5
6
"threadA" #9 prio=5 os_prio=0 tid=0x00007fed1c0ed800 nid=0x4d5e waiting on condition [0x00007fed04315000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:338)
at Main$1.run(Main.java:9)
at java.lang.Thread.run(Thread.java:748)

实验代码17

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.concurrent.locks.LockSupport;

public class Main {
public static void main(String[] args) throws InterruptedException {

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
LockSupport.parkUntil(System.currentTimeMillis() + 10 * 1000L);

System.out.println("hello world");
}
}, "threadA");

threadA.start();
}
}

程序运行结果如下:

1
hello world

3.2.6、Condition接口的await()/awaitUninterruptibly()/await(long time, TimeUnit unit)/awaitNano(long nanosTimeout)/awaitUntil(Date deadline)/signal()/signalAll()实例方法

对于Condition接口,在Java并发包中已存在两个实现,分别是java.util.concurrent.locks.AbstractQueuedLongSynchronizer.ConditionObjectjava.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject,查看两者实现,发现都通过调用LockSupport类的park/parkNanos/parkUtil方法实现await()/awaitUninterruptibly()/await(long time, TimeUnit unit)/awaitNano(long nanosTimeout)/awaitUntil(Date deadline)方法的核心逻辑,通过调用LockSupport类的unpark方法实现signal()/signalAll()方法的核心逻辑。

仅限于基于上述两个已存在类,可以有结论:

  • 调用await()/awaitUninterruptibly()会进入WAITING状态
  • 调用await(long time, TimeUnit unit)/awaitNano(long nanosTimeout)/awaitUntil(Date deadline)会进入TIMED_WAITING状态

另外值得叙述的是awaitUninterruptibly()方法(源码如下),它针对中断操作的处理步骤是:在被中断信号唤醒后,立即无条件复位中断标志位,然后判断while退出条件是否得到满足,如果满足则退出,否则继续挂起;而await()/await(long time, TimeUnit unit)/awaitNano(long nanosTimeout)/awaitUntil(Date deadline)方法针对中断操作的处理步骤是:在被中断信号唤醒后,立即无条件复位中断标志位,然后直接退出while循环。

1
2
3
4
5
6
7
8
9
10
11
12
public final void awaitUninterruptibly() {
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean interrupted = false;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if (Thread.interrupted())
interrupted = true;
}
if (acquireQueued(node, savedState) || interrupted)
selfInterrupt();
}

四、中断

中断的本质十分简单:“发出唤醒信号”和“设置中断标志位”。如何处理“唤醒信号”和“中断标志位”由后续具体逻辑具体负责。
发出的唤醒信号分为3类:

  • 针对Thread.sleep的唤醒信号
  • 针对Object.wait的唤醒信号,等价于Object.notify
  • 针对LockSupport.park的唤醒信号,等价于LockSupport.unpark

在Thread类中跟中断有关的方法有3个:

  • interrupt():在线程存活期调用,否则无实际意义
  • interrupted():如果设置了中断标志位则返回true,复位中断标志位;否则返回false
  • isInterrupted():如果设置了中断标志位则返回true,否则返回false。需要注意的是:并不会复位中断标志位

4.1、针对Thread类的sleep方法

一次性唤醒信号。

4.1.1、后序

一次性唤醒信号生效消失,线程被唤醒后检测到设置了中断标志位,抛出InterruptedException异常,复位中断标志位。

实验代码18

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main {
public static void main(String[] args) throws InterruptedException {

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10000L);
} catch (InterruptedException e) {
System.out
.println(System.currentTimeMillis() + ":[" + Thread.currentThread().isInterrupted() + "]");
}
}
}, "threadA");

threadA.start();

Thread.sleep(5000);

System.out.println(System.currentTimeMillis());
threadA.interrupt();
}
}

程序运行结果如下:

1
2
1609836534638
1609836534639:[false]

4.1.2、先序

一次性唤醒信号丢失消失,后续线程调用Thread.sleep方法时发现设置了中断标志位,直接唤醒,抛出InterruptedException异常,复位中断标志位。

实验代码19

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
public class Main {
public static void main(String[] args) throws InterruptedException {

Switch aSwitch = new Switch();

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
while (!aSwitch.flag) {

}
System.out.println("hello world");

try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out
.println(System.currentTimeMillis() + ":[" + Thread.currentThread().isInterrupted() + "]");
}

}
}, "threadA");

threadA.start();

threadA.interrupt();

System.out.println(threadA.isInterrupted());

aSwitch.flag = true;
}
}

class Switch {
volatile boolean flag = false;
}

程序运行结果如下:

1
2
3
true
hello world
1609836587473:[false]

4.2、针对Object类的wait方法

一次性唤醒信号。

再次强调下:如上所述,我们知道,线程A调用对象obj的wait方法进入WAITING或者TIMED_WAITING状态,后续尝试唤醒时(线程B调用执行对象obj的notify/notifyAll方法;设定的超时时间到期;虚假唤醒),只有当线程A再次申请到obj对应的synchronized锁才被成功唤醒,中断唤醒情形也不例外

4.2.1、后序

一次性唤醒信号生效消失,线程被唤醒后(需要再次获取到synchronized锁)检测到设置了中断标志位,抛出InterruptedException异常,复位中断标志位。

实验代码20

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
public class Main {
public static void main(String[] args) throws InterruptedException {

Object obj = new Object();

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
System.out
.println(System.currentTimeMillis() + ":[" + Thread.currentThread().isInterrupted() + "]");
}
}
}
}, "threadA");

threadA.start();

Thread.sleep(5000);

Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
}

System.out.println(System.currentTimeMillis() + ":[thread sleep finished]");
}
}
}, "threadB");

threadB.start();

Thread.sleep(5000);

System.out.println(System.currentTimeMillis() + ":[interrupt send]");
threadA.interrupt();
}
}

程序运行结果如下:

1
2
3
1609753806368:[interrupt send]
1609753861368:[thread sleep finished]
1609753861369:[false]

4.2.2、先序

一次性唤醒信号丢失消失,后续线程调用wait方法发现设置了中断标志位,直接唤醒(需要再次获取到synchronized锁,不过笔者不是很确定——在该种情形中,是“先释放锁,再获取锁”,还是“发现设置了中断标志位直接不释放锁”),抛出InterruptedException异常,复位中断标志位。

实验代码21

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
public class Main {
public static void main(String[] args) throws InterruptedException {

Switch aSwitch = new Switch();

Object objA = new Object();

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
while (!aSwitch.flag) {

}
System.out.println("hello world");

synchronized (objA) {
try {
objA.wait();
} catch (InterruptedException e) {
System.out
.println(System.currentTimeMillis() + ":[" + Thread.currentThread().isInterrupted() + "]");
}
}

}
}, "threadA");

threadA.start();

threadA.interrupt();

System.out.println(threadA.isInterrupted());

aSwitch.flag = true;
}
}

class Switch {
volatile boolean flag = false;
}

程序运行结果如下:

1
2
3
true
hello world
1609838232636:[false]

4.3、针对LockSupport类的park()/parkNanos(long nanos)/parkUtil(long deadline)方法

标志位唤醒信号。

4.3.1、后序

标志位唤醒信号生效消失,线程被唤醒后虽然检测到设置了中断标志位,但是不会抛出InterruptedException异常,也不会复位中断标志位。

实验代码22

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.concurrent.locks.LockSupport;

public class Main {
public static void main(String[] args) throws InterruptedException {

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
LockSupport.park();

System.out.println(System.currentTimeMillis() + ":[" + Thread.currentThread().isInterrupted() + "]");
}
}, "threadA");

threadA.start();

Thread.sleep(5000);

System.out.println(System.currentTimeMillis());
threadA.interrupt();
System.out.println(threadA.isInterrupted());
}
}

程序运行结果如下:

1
2
3
1609805900180
true
1609805900180:[true]

4.3.2、先序

标志位唤醒信号不会丢失,后续线程调用LockSupport类的park()/parkNanos(long nanos)/parkUtil(long deadline)发现存在唤醒信号,标志位唤醒信号生效消失,线程被唤醒后虽然检测到设置了中断标志位,但是不会抛出InterruptedException异常,也不会复位中断标志位。
在没有标志位唤醒信号的情形下,后续线程调用LockSupport类的park()/parkNanos(long nanos)/parkUtil(long deadline)方法发现设置了中断标志位,直接唤醒,但是不会抛出InterruptedException异常,也不会复位中断标志位。

实验代码23

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
import java.util.concurrent.locks.LockSupport;

public class Main {
public static void main(String[] args) throws InterruptedException {

Switch aSwitch = new Switch();

Object obj = new Object();

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
while (!aSwitch.flag) {

}

System.out.println("hello");

synchronized (obj) {
try {
LockSupport.park();
} catch (Exception e) {
System.out
.println(System.currentTimeMillis() + ":[" + Thread.currentThread().isInterrupted() + "]");
}

System.out.println("world");
}

LockSupport.park();

System.out.println("hello world again");
}
}, "threadA");

threadA.start();

threadA.interrupt();

System.out.println(threadA.isInterrupted());

aSwitch.flag = true;
}
}

class Switch {
volatile boolean flag = false;
}

程序运行结果如下:

1
2
3
4
true
hello
world
hello world again

五、死锁

5.1、狭义

死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

Java死锁产生的四个必要条件:

  • 互斥使用,即当资源被一个线程占有使用时,别的线程不能使用
  • 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放
  • 请求和保持,即当资源请求者在请求其他资源的同时保持对原有资源的占有
  • 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源,这样就形成了一个等待环路

当上述四个条件都成立的时候,便形成死锁,在死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

以上引用自[11]。

根据以上定义,可使用synchronized锁或者Lock显式锁构造死锁案例,接下来根据synchronized锁构造一个死锁案例。

实验代码24

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
public class Main {
public static void main(String[] args) throws InterruptedException {

Object objA = new Object();
Object objB = new Object();

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (objA) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}

synchronized (objB) {
System.out.println("hello");
}
}
}
}, "threadA");

Thread.sleep(5000);
threadA.start();

Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
synchronized (objB) {
synchronized (objA) {
System.out.println("world");
}
}
}
}, "threadB");
threadB.start();
}
}

线程栈信息如下所示,发现jstack能够直接侦测到该种死锁:

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
"threadB" #10 prio=5 os_prio=0 tid=0x00007f39bc0e0000 nid=0x5774 waiting for monitor entry [0x00007f39a67ac000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Main$2.run(Main.java:32)
- waiting to lock <0x000000078665b048> (a java.lang.Object)
- locked <0x000000078665b058> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)

"threadA" #9 prio=5 os_prio=0 tid=0x00007f39bc0de000 nid=0x5773 waiting for monitor entry [0x00007f39a68ad000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Main$1.run(Main.java:18)
- waiting to lock <0x000000078665b058> (a java.lang.Object)
- locked <0x000000078665b048> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)

//这里省略

Found one Java-level deadlock:
=============================
"threadB":
waiting to lock monitor 0x00007f398c003a18 (object 0x000000078665b048, a java.lang.Object),
which is held by "threadA"
"threadA":
waiting to lock monitor 0x00007f398c006568 (object 0x000000078665b058, a java.lang.Object),
which is held by "threadB"

Java stack information for the threads listed above:
===================================================
"threadB":
at Main$2.run(Main.java:32)
- waiting to lock <0x000000078665b048> (a java.lang.Object)
- locked <0x000000078665b058> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"threadA":
at Main$1.run(Main.java:18)
- waiting to lock <0x000000078665b058> (a java.lang.Object)
- locked <0x000000078665b048> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

5.2、广义

死锁是这样一种情形:多个线程同时被阻塞,它们互相循环依赖对方的动作才能被唤醒。由于线程被无限期地阻塞,因此程序不可能正常终止。

实验代码25

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class Main {
public static void main(String[] args) throws InterruptedException {

Object objA = new Object();
Object objB = new Object();
Object objC = new Object();

Thread threadA = new Thread(new Runnable() {

public void run() {
synchronized (objA) {
try {
objA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

synchronized (objB) {
objB.notify();
}

System.out.println("A");
}
}, "threadA");

Thread threadB = new Thread(new Runnable() {

public void run() {
synchronized (objB) {
try {
objB.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

synchronized (objC) {
objC.notify();
}

System.out.println("B");
}
}, "threadB");

Thread threadC = new Thread(new Runnable() {

public void run() {
synchronized (objC) {
try {
objC.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

synchronized (objA) {
objA.notify();
}

System.out.println("C");
}
}, "threadC");

Thread.sleep(5000);
threadA.start();

Thread.sleep(5000);
threadB.start();

Thread.sleep(5000);
threadC.start();
}
}

六、其他

6.1、挂起线程T0被线程T1唤醒的跨线程内存可见性

这里讨论挂起线程T0被线程T1唤醒时的跨线程内存可见性问题,具体是:T1中的唤醒前动作集T1AS与T0中的被唤醒后动作集T0AS之间的happens-before规则(需要注意的是,讨论对象从“内存可见性”到“happens-before规则”是因为:在内存可见性范畴,“happens-before规则”虽然只是其中一类规则,还有很多其他规则,比如“final关键词提供的内存可见语义规则”,但是“happens-before规则”相对来说最具有应用价值)。

根据“3.2、挂起 <-> RUNNABLE的转移条件”小节内容,可知挂起线程T0的方法有6个:

  • Thread类的sleep方法,唤醒方式有:超时自唤醒,T1调用T0的interrupt()方法
  • synchronized方法/语句块,唤醒方式有:T1释放synchronized锁
  • Object类的wait方法,唤醒方式有:虚假唤醒,超时自唤醒,T1调用Object类的notify()/notifyAll()方法或者调用T0的interrupt()方法
  • Thread类的join实例方法,唤醒方式有:超时自唤醒,T1线程(T1就是T0所等待的线程,即T0线程调用T1.join()挂起)执行完成进入TERMINATED状态,T1(T1可以是T0所等待的线程,也可以不是,一般是后者)调用T0的interrupt()方法
  • LockSupport类的park/parkNano/parkUntil方法,唤醒方式有:虚假唤醒,超时自唤醒,T1调用LockSupport类的unpark(T0)方法或者调用T0的interrupt()方法
  • Condition接口的await/awaitUninterruptibly/awaitNano/awaitUntil实例方法,唤醒方式有:虚假唤醒,超时自唤醒,T1调用Condition接口的signal()/signalAll()方法或者调用T0的interrupt()方法(需要注意的是,awaitUninterruptibly方法不响应中断)

在继续讨论之前,先作几点说明

  • 接下来,以“hb”简指“happens-before”
  • 下表中的NO意指:T1中的中断唤醒前动作集T1AS与T0中的被唤醒后动作集T0AS没有必然的内存可见性关系,自然也没有必然的hb规则
  • Condition实例对象关联的锁一般是指ReentrantLock锁,这里也基于此进行说明。跟synchronized锁一样,ReentrantLock锁的使用也遵循“监视器锁”hb规则,具体证明可参见《Lock接口》
  • 对于wait-notify/notifyAllawait/awaitUninterruptibly/awaitNano/awaitUntil-signal/signalAll过程的hb规则,须具体情况具体分析,核心基于“监视器锁”hb规则,再次提醒下:在调用waitawait/awaitNano/awaitUntil方法后立即中断的情形中,锁的申请和释放过程是有点特殊的
T0挂起方法 T0自唤醒 T1非中断唤醒 T1中断唤醒
Thread类的sleep方法 NO 不存在T1非中断唤醒情形 NO
synchronized方法/语句块 不可能情形 T1释放相应的synchronized锁,关于内存可见性,遵循监视器锁hb规则 NO
Object类的wait方法 NO 见“论述1” NO
Thread类的join方法 NO T0线程调用T1.join()挂起,T1执行完成进入TERMINATED状态唤醒T0,关于内存可见性,遵循join() hb规则

根据“3.2.4、Thread类的join实例方法”可知,join() hb规则可简单由“论述1”中的wait/notify/notifyAll hb规则推得,因此,join() hb规则本质也是来源于监视器锁hb规则
NO
LockSupport类的park/parkNano/parkUntil方法 NO T1调用LockSupport类的unpark(T0)方法,关于内存可见性,T1中的unpark()方法前动作集T1AS与T0中的被唤醒后动作集T0AS没有必然的内存可见性关系[8] NO
Condition的await/awaitUninterruptibly/awaitNano/awaitUntil方法 NO 见“论述2” NO

论述1
T0调用wait方法挂起,T1调用notify()/notifyAll()方法尝试唤醒,

  • 未成功唤醒:不可能出现的情形。因为“根据已知前提,唤醒信号必定发送,调用notify()/notifyAll()方法后最终必定会自动隐式释放掉synchronized锁,成功唤醒的条件得到满足”
  • 成功唤醒:调用notify()/notifyAll()方法后最终必定会自动隐式释放掉synchronized锁,该锁被T0获取而成功唤醒(实际可能是形如“T1释放锁 Tm获取锁 Tm释放锁 Tn获取锁 Tn释放锁 … T0获取锁”过程,但是根据“传递性”hb规则,不影响证明),基于“程序顺序”,“监视器锁”和“传递性”这3个hb规则,可推导得:T1调用notify()/notifyAll()方法之前的操作(更准确的说,T1在调用notify()/notifyAll()后最终隐式释放掉synchronized锁之前的操作) happens-before 于T0调用wait方法被成功唤醒之后的操作。据此,在实验代码26中,有“//1 -hb-> //2 -hb-> //3 -hb-> //4 -hb-> //5 -hb-> //6 -hb-> //7”

论述2
T0调用Condition的await/awaitUninterruptibly/awaitNano/awaitUntil方法挂起,T1调用signal()/signalAll()方法尝试唤醒,

  • 未成功唤醒:根据已知前提,唤醒信号必定发送,故未成功唤醒只能是因为T1中最后未释放对应的ReentrantLock锁,此时无谓谈论happens-before关系。但是根据如下“ReentrantLock锁使用的惯用法”,该种情形理论上不会出现
  • 成功唤醒:调用signal()/signalAll()方法后最终在finally语句中显式释放掉ReentrantLock锁,该锁被T0获取而成功唤醒(实际可能是形如“T1释放锁 Tm获取锁 Tm释放锁 Tn获取锁 Tn释放锁 … T0获取锁”过程,但是根据“传递性”hb规则,不影响证明),基于“程序顺序”,“监视器锁”和“传递性”这3个hb规则,可推导得:T1调用signal()/signalAll()方法之前的操作(更准确的说,T1在调用signal()/signalAll()后最终显式释放掉ReentrantLock锁之前的操作) happens-before 于T0调用await/awaitUninterruptibly/awaitNano/awaitUntil方法被成功唤醒之后的操作

ReentrantLock锁使用的惯用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockUseExample {
ReentrantLock lock = new ReentrantLock();

public void lockExample() {
lock.lock();

try {
// doSomething();
} finally {
lock.unlock();
}
}
}

实验代码26

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
public class Main {
public static void main(String[] args) throws InterruptedException {

Object obj = new Object();

Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
// 被唤醒,这里重新获得synchronized锁
obj.wait(); // 4

System.out.println("5"); // 5
System.out.println("6"); // 6
System.out.println("7"); // 7
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "t0");

t0.start();

Thread.sleep(5000);

Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
obj.notify();

try {
Thread.sleep(6000L);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("1"); // 1
System.out.println("2"); // 2
} // 3
}
}, "t1");
t1.start();
}
}

6.2、惯用法

Object类的wait和LockSupport类的park方法使用的惯用形式为:在一个while语句块中调用wait方法和park方法,被唤醒后如果不满足while循环退出条件继续挂起。可参见实验代码27。
以上惯用形式的合理和必要之处在于:调用waitpark方法的原始意图本就在于等待某个条件的达成,如果被非法唤醒而条件未达成则会导致违背这种原始意图,通过while语句块可以避免这种情形。
非法唤醒的情形包括两种:

  • 代码实现Bug,误调用Object类的notify/notifyAll方法/LockSupport类的unpark方法、设定的唤醒超时时间到期、误调用Thread类的interrupt()方法。从这个角度来看,如果不用惯用法,代码健壮性差
  • waitpark方法都有一个“虚假唤醒”的唤醒情形。从这个角度来看,如果不用惯用法,代码存在漏洞

实验代码27

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
import java.util.concurrent.locks.LockSupport;

/**
* 只是示例,没有考虑代码健壮性(包括线程安全性)
*/
public class Main {
Object obj = new Object();

volatile boolean flag = false;
volatile Thread parkThread;

public void waitMethod() {
synchronized (obj) {
while (!flag) {
try {
obj.wait();
} catch (InterruptedException e) {

}
}
}
}

public void notifyMethod() {
synchronized (obj) {
obj.notify();
flag = true;
}
}

public void parkMethod() {
while (!flag) {
parkThread = Thread.currentThread();
LockSupport.park();
}
}

public void unparkMethod() {
flag = true;
LockSupport.unpark(parkThread);
}
}

6.3、垃圾回收

创建Thread实例对象,并开启对应线程后,它会被注册到“某处”,因此在线程存活期内是引用可达的,并不会被垃圾回收;线程结束后相应的Thread实例对象才会被解除注册,此时可被垃圾回收。

6.4、main主线程与普通线程的异同

main主线程与普通线程相比,除了是入口线程外,并无二致。

6.5、线程内异常处理

Java线程内抛出异常(包括“错误”,接下来未作特别声明,“异常”都包括“错误”)时,有两条运行路径。

6.5.1、异常在线程内被catch住

正确合理的应对方式。

6.5.2、异常未在线程内被catch住

线程内异常未在线程内被catch住,即逃逸到线程外,这会导致该线程死亡,但不会影响所在Java进程。不过在线程死亡之前,会去执行Thread类的“private void dispatchUncaughtException(Throwable e)”方法(“e”指代未被处理异常)。
默认会调用ThreadGroup类下uncaughtException()方法,可通过setUncaughtExceptionHandler()方法自定义设置“UncaughtExceptionHandler”实例对象,从而自定义对未处理异常的处理逻辑。

特别需要注意的是:Java线程运行在独立的线程空间,内部未处理的异常不能被其它线程处理,示例见以下两个示例代码。

示例代码如下:

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

public static void main(String[] args) throws InterruptedException {
Thread thread = null;
try {
thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
System.out.println("hello world");

// 以下抛出异常内部未能处理,执行Thread类的“private void dispatchUncaughtException(Throwable e)”方法,该异常作为参数传入
throw new RuntimeException("hello world exception");
} catch (NullPointerException e) {
// 没有抓住RuntimeException异常
e.printStackTrace();
}

System.out.println("can not run to here");

try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});

thread.start();
} catch (RuntimeException e) {
// 线程内部异常不能被其它线程处理
e.printStackTrace();
}

Thread.sleep(100);

// 没有抓住异常,线程直接结束
System.out.println("thread is alive ? " + thread.isAlive());
}
}
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
import java.lang.Thread.UncaughtExceptionHandler;

public class ThreadExceptionUncaughtExceptionHandler {

public static void main(String[] args) throws InterruptedException {
Thread thread = null;
try {
thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
System.out.println("hello world");
throw new RuntimeException("hello world exception");
} catch (NullPointerException e) {
// 没有抓住RuntimeException异常
e.printStackTrace();
}

System.out.println("can not run to here");

try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "dslztx-thread-1");

thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("异常catch了,当前线程为:" + Thread.currentThread().getName() + ",异常线程为:" + t.getName()
+ ",异常信息为:" + e.getStackTrace());
}
});

thread.start();
} catch (RuntimeException e) {
// 线程内部异常不能被其它线程处理
e.printStackTrace();
}

Thread.sleep(100);

// 没有抓住异常,线程直接结束
System.out.println("thread is alive ? " + thread.isAlive());
}
}

6.6、调用Object.wait或者Thread.join方法挂起查看所打印jstack命令结果的一个注意点

调用Object.wait或者Thread.join(本质通过调用Object.wait实现)方法挂起查看所打印jstack命令结果的一个注意点:虽然显示某个synchronized锁被获取,但实际上调用wait方法挂起后是释放掉该锁的,比如在如下jstack命令结果中,“threadA”在挂起时已经释放掉“0x000000078665afb8”指代的synchronized锁。

1
2
3
4
5
6
7
8
"threadA" #9 prio=5 os_prio=0 tid=0x00007f62e00f6000 nid=0x29f8 in Object.wait() [0x00007f62ca2a7000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000078665afb8> (a java.lang.Object)
at java.lang.Object.wait(Object.java:502)
at Main$1.run(Main.java:11)
- locked <0x000000078665afb8> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)

参考文献

[1]《Java并发编程的艺术》
[2]https://www.cnblogs.com/YDDMAX/p/5208561.html
[3]https://blog.csdn.net/qq_38244610/article/details/106106276
[4]https://stackoverflow.com/questions/9577231/how-to-check-the-state-of-linux-threads
[5]https://cgiirw.github.io/2018/05/27/Interrupt_Ques/
[6]https://cgiirw.github.io/2018/05/17/Thread01/
[7]https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.2.1
[8]https://stackoverflow.com/questions/2475067/java-locksupport-memory-consistency
[9]https://blogs.oracle.com/dave/a-race-in-locksupport-park-arising-from-weak-memory-models
[10]https://cgiirw.github.io/2018/10/17/Blocked03/
[11]https://www.runoob.com/java/thread-deadlock.html

您的支持将鼓励我继续分享!