Java中的死锁
锁的合理使用能够保证共享数据的安全性,但是 使用不当也会可能引起死锁。
1. 死锁概念
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
2. 死锁原因
- 互斥条件(Mutual Exclusion)
资源一次只能被一个线程独占使用。 - 持有并等待(Hold and Wait)
线程在持有至少一个资源的同时,还在等待其他线程持有的资源。 - 不可抢占(No Preemption)
资源不能被强制释放,只能由持有它的线程主动释放。 - 循环等待(Circular Wait)
存在一个线程等待链,每个线程都在等待下一个线程持有的资源。
当这四个条件同时满足时,死锁必然发生。
3. 死锁演示
下面的一段代码演示了死锁:
public static void main(String[] args) {Object lockA = new Object();Object lockB = new Object();new Thread(() -> {synchronized (lockA) {System.out.println(Thread.currentThread().getName() + ", 获取了🔒A");try {sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (lockB) {System.out.println(Thread.currentThread().getName() + ", 获取了🔒B");}}}, "线程1").start();new Thread(() -> {synchronized (lockB) {System.out.println(Thread.currentThread().getName() + ", 获取了🔒B");try {sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (lockA) {System.out.println(Thread.currentThread().getName() + ", 获取了🔒A");}}}, "线程2").start();
}
执行结果:
4. 死锁检测
4.1 命令
jps -l
jstack pid
jstack 6612
4.2 图形工具 jconsole
5. 避免死锁
- 避免一个线程同时获取多个不同的锁
- 避免一个线程在锁内同时占用多个资源 尽量保证每个锁只占一个资源
- 尝试使用带超时时间到锁 ,例如 lock.tryLock(timeout) 来替代内部锁机制
- 对于数据库锁 加锁和解锁必须在同一个数据库连接里,否则会出现解锁失败的情况
- 按固定顺序获取锁。
- 使用超时机制(
tryLock
)。 - 减少锁的持有时间和粒度。
- 利用高级并发工具替代显式锁。
通过合理设计代码结构、遵循锁顺序约定,以及利用Java并发工具包,可有效避免死锁问题。