多线程环境下,死锁即两个或两个以上的线程去争夺同一个共享资源,而导致互相等待的情况。
要产生死锁,必须满足如下四个条件:
- 互斥条件,共享资源x和y只能被一个线程占有
- 请求和保持条件,T1持有x,并请求y,但不释放x
- 不可抢占条件,T1持有x,即使T2需要x,也不能强行从T1那里夺取x。资源不能被强行从持有它的线程中夺取,只有线程自愿释放它所持有的资源,其他线程才能获得这些资源。
- 循环等待条件,线程T1等到线程T2占用的资源,线程T2等到线程T1占用的资源,循环等待。
死锁已经产生,只能通过外部的干预来解决,比如重启或kill了线程。
出现死锁后,可以通过jstack命令去导出线程的dump日志,然后从dump日志中定位到具体的死锁程序代码。
实例:
- 使用jps找到java的pid
jps
12345 MyJavaApp
67890 Jps
- 使用jstack将输出重定向到文件
jstack 12345 > stack_trace.txt
- 使用cat查看输出文件
cat stack_trace.txt"main" #1 prio=5 os_prio=0 tid=0x00007f8c5400b800 nid=0x1b03 waiting on condition [0x00007f8c5d1eb000]java.lang.Thread.State: TIMED_WAITING (sleeping)at java.lang.Thread.sleep(Native Method)at MyJavaApp.main(MyJavaApp.java:15)"Worker-1" #2 prio=5 os_prio=0 tid=0x00007f8c5400c800 nid=0x1b04 runnable [0x00007f8c5d3eb000]java.lang.Thread.State: RUNNABLEat MyJavaApp.doWork(MyJavaApp.java:30)at MyJavaApp.run(MyJavaApp.java:25)at java.lang.Thread.run(Thread.java:748)
- 分析上面的stack_trace.txt
jstack 的输出会列出每个线程的堆栈信息,包括线程 ID、线程状态、锁信息等。示例如下:
"main" #1 prio=5 os_prio=0 tid=0x00007f8c5400b800 nid=0x1b03 waiting on condition [0x00007f8c5d1eb000]java.lang.Thread.State: TIMED_WAITING (sleeping)at java.lang.Thread.sleep(Native Method)at MyJavaApp.main(MyJavaApp.java:15)"Worker-1" #2 prio=5 os_prio=0 tid=0x00007f8c5400c800 nid=0x1b04 runnable [0x00007f8c5d3eb000]java.lang.Thread.State: RUNNABLEat MyJavaApp.doWork(MyJavaApp.java:30)at MyJavaApp.run(MyJavaApp.java:25)at java.lang.Thread.run(Thread.java:748)
“main” 和 “Worker-1”:线程的名称。
#1 和 #2:Java 线程 ID,这个 ID 是由 Java 虚拟机分配的,通常是一个递增的长整型值。Java 里的线程 ID 可以通过 Thread.getId() 获取,并与 jstack 输出中的 # 后的数字对应。
tid=0x00007f8c5400b800 和 tid=0x00007f8c5400c800:线程对象的内存地址。
nid=0x1b03 和 nid=0x1b04:本地线程 ID(Native Thread ID),由操作系统分配。jstack 输出中的 nid 字段即为本地线程 ID。它是用于操作系统级的线程管理和调度。nid,可以与操作系统级线程工具输出的 LWP 对应。
例如:
终端上使用命令
ps -L -p <pid>输出:
PID LWP TTY TIME CMD
12345 12345 pts/0 00:00:00 java
12345 12346 pts/0 00:00:00 java
12345 12347 pts/0 00:00:00 java
LWP 是 Light Weight Process ID,对应于本地线程 ID(十进制形式)。要将 nid 与 LWP 对应起来,可以将十六进制的 nid 转换为十进制。例如,nid=0x1b03 转换为十进制是 6915。
也可以通过破坏死锁产生的四个条件:
- 互斥条件是锁本身的特征,无法被破坏
- 线程第一次执行时,一次性申请所有的共享资源来破坏请求和保持条件
- 占用部分共享资源的同时,在进一步申请其他资源的时候,如果申请不到就主动释放它已有的资源。破坏不可抢占条件。虽然看起来这种策略也破坏了请求和保持条件,但实际上,它是通过破坏不可抢占条件来间接打破请求和保持条件的。关键点在于,资源的释放行为不再仅仅由持有线程的意愿决定,而是受到资源分配策略的影响,这直接破坏了不可抢占条件。
- 给共享资源编号,线程按顺序去申请资源就可以破坏循环等待条件。