一、线程基本含义 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线程的R
和S
”,“Java线程的BLOCKED
,WAITING
和TIMED_WAITING
都对应Linux线程的S
”。实验代码1证明当Java线程处于“IO_WAIT(等待IO)”情形时,其Java线程状态为RUNNABLE
,对应的Linux线程状态为S
挂起
虚状态包括BLOCKED
,WAITING
和TIMED_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()); } }
程序运行结果如下:
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(); 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锁。
实验代码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(); 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才能被成功唤醒:
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); } }
程序运行结果如下:
实验代码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); LockSupport.unpark(threadA); } }
程序运行结果如下:
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(); } }
程序运行结果如下:
在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(); } }
程序运行结果如下:
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.ConditionObject
和java.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/notifyAll
和await/awaitUninterruptibly/awaitNano/awaitUntil-signal/signalAll
过程的hb规则,须具体情况具体分析,核心基于“监视器锁”hb规则,再次提醒下:在调用wait
和await/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 { } 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 { obj.wait(); System.out.println("5" ); System.out.println("6" ); System.out.println("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" ); System.out.println("2" ); } } }, "t1" ); t1.start(); } }
6.2、惯用法 Object类的wait
和LockSupport类的park
方法使用的惯用形式为:在一个while语句块中调用wait
方法和park
方法,被唤醒后如果不满足while循环退出条件继续挂起。可参见实验代码27。 以上惯用形式的合理和必要之处在于:调用wait
和park
方法的原始意图本就在于等待某个条件的达成,如果被非法唤醒而条件未达成则会导致违背这种原始意图,通过while语句块可以避免这种情形。 非法唤醒的情形包括两种:
代码实现Bug,误调用Object类的notify/notifyAll
方法/LockSupport类的unpark
方法、设定的唤醒超时时间到期、误调用Thread类的interrupt()
方法。从这个角度来看,如果不用惯用法,代码健壮性差
wait
和park
方法都有一个“虚假唤醒”的唤醒情形。从这个角度来看,如果不用惯用法,代码存在漏洞
实验代码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" ); throw new RuntimeException ("hello world exception" ); } catch (NullPointerException e) { 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) { 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