多线程死锁
思路
原因:
多线程 + 共享资源
互斥:一个资源,只能被一个线程持有
持有并等待:
非抢占
循环等待
方案:
多线程顺序获取资源
+
tryLock 设置获取锁超时时间
使用并发集合或类:concurrenthashmap AutonomicLong
减小持有锁多粒度
ReentrantLock
:
支持可中断锁、公平锁、尝试锁等
多线程死锁(Deadlock)详解
死锁是多线程编程中最经典的问题之一,指 两个或多个线程互相持有对方需要的资源,导致所有线程无限等待。以下是死锁的核心概念、产生条件、排查方法和解决方案。
1. 死锁的四个必要条件
死锁发生必须同时满足以下四个条件(缺一不可):
互斥条件(Mutual Exclusion)
资源一次只能被一个线程占用(如锁、文件句柄等)。占有并等待(Hold and Wait)
线程持有至少一个资源,同时等待获取其他被占用的资源。非抢占条件(No Preemption)
已分配给线程的资源不能被其他线程强制夺取,必须由线程主动释放。循环等待(Circular Wait)
存在一个线程的循环等待链,每个线程都在等待下一个线程占用的资源。
2. 死锁代码示例(Java)
java
public class DeadlockExample {private static final Object lock1 = new Object();private static final Object lock2 = new Object();public static void main(String[] args) {Thread thread1 = new Thread(() -> {synchronized (lock1) {System.out.println("Thread1 holds lock1");try { Thread.sleep(100); } catch (InterruptedException e) {}synchronized (lock2) {System.out.println("Thread1 holds lock2");}}});Thread thread2 = new Thread(() -> {synchronized (lock2) {System.out.println("Thread2 holds lock2");synchronized (lock1) {System.out.println("Thread2 holds lock1");}}});thread1.start();thread2.start();} }
输出结果:
text
Thread1 holds lock1 Thread2 holds lock2 (程序卡死,无法继续)
原因:
Thread1
持有lock1
,等待lock2
。Thread2
持有lock2
,等待lock1
。形成 循环等待,导致死锁。
3. 如何检测死锁?
(1) 命令行工具(Linux/Java)
jstack
(Java 线程分析工具):bash
jstack <PID> # 查看线程堆栈,搜索 "deadlock"
jconsole
或 VisualVM:
图形化工具中可直接检测到死锁线程。
(2) 代码中主动检测
java
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); long[] threadIds = threadMXBean.findDeadlockedThreads(); if (threadIds != null) {System.out.println("Detected deadlock!"); }
4. 解决死锁的常见方法
(1) 破坏死锁的必要条件
避免互斥:
使用无锁数据结构(如ConcurrentHashMap
)。避免占有并等待:
一次性申请所有资源(如通过全局锁)。允许抢占:
使用tryLock()
超时机制(见下文)。避免循环等待:
按固定顺序获取锁(如总是先lock1
后lock2
)。
(2) 使用超时机制
java
if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {try {if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {try {// 执行业务逻辑} finally {lock2.unlock();}}} finally {lock1.unlock();} }
(3) 锁顺序全局化
java
// 总是先获取 hash值小的锁 int hash1 = System.identityHashCode(lock1); int hash2 = System.identityHashCode(lock2);if (hash1 < hash2) {synchronized (lock1) {synchronized (lock2) { /* ... */ }} } else {synchronized (lock2) {synchronized (lock1) { /* ... */ }} }
(4) 使用高级并发工具
ReentrantLock
:
支持可中断锁、公平锁、尝试锁等。Semaphore
:
控制资源访问数量。CyclicBarrier
/CountDownLatch
:
协调多线程执行顺序。
5. 预防死锁的最佳实践
尽量减少同步代码块的范围。
避免嵌套锁(如一个同步方法调用另一个同步方法)。
使用线程池,避免无限制创建线程。
设计时考虑锁顺序,避免循环等待。
监控工具预警(如 APM 工具检测线程阻塞)。
总结
关键点 | 说明 |
---|---|
死锁条件 | 互斥、占有等待、非抢占、循环等待(四者缺一不可)。 |
检测工具 | jstack 、jconsole 、VisualVM。 |
解决方案 | 破坏必要条件、超时机制、锁顺序全局化、使用高级并发工具。 |
预防策略 | 减少锁粒度、避免嵌套锁、设计时规划锁顺序。 |
死锁是并发编程中的难点,但通过合理设计和工具辅助,可以有效避免和解决!