关于死锁问题的学习总结

死锁产生的完整链条:

复制代码
互斥 → 持有并等待 → 非抢占 → 循环等待 → 死锁
  1. 互斥:资源只能独占使用(必要条件)

  2. 持有并等待:线程持有资源同时请求新资源

  3. 非抢占:已分配资源不能被强制剥夺

  4. 循环等待:线程间形成资源等待环

破坏各个条件的策略对比:

破坏的条件 可行性 Java实现方法 缺点
互斥 困难 使用无锁数据结构、原子变量 不适用所有场景
持有并等待 可行 原子性地获取所有资源 可能降低并发性
非抢占 可行 使用tryLock、超时机制 需要回退重试逻辑
循环等待 最可行 固定资源获取顺序 需要全局排序策略

实际工程建议

避免死锁的最佳实践:

  1. 尽量使用单锁:减少多锁交互的机会

  2. 固定锁顺序:全局约定锁的获取顺序

  3. 使用超时锁:用Lock接口的tryLock方法代替synchronized,这样可以设置超时时间,避免无限期等待

  4. 使用更高级的并发工具:如使用java.util.concurrent包中的并发类,它们设计时已经考虑了死锁问题

  5. 静态分析工具:使用FindBugs、SpotBugs检测潜在死锁

  6. 监控和检测:使用ThreadMXBean.findDeadlockedThreads()

实际使用 -- 典型的死锁示例:

如果两个线程分别同时执行method1和method2,可能会发生以下情况:

  1. 线程A进入method1,获得lock1。

  2. 线程B进入method2,获得lock2。

  3. 线程A在获得lock1的状态下试图获取lock2,但lock2被线程B持有,所以线程A等待。

  4. 线程B在获得lock2的状态下试图获取lock1,但lock1被线程A持有,所以线程B等待。

这样,两个线程互相等待对方释放锁,导致死锁。

进一步带入到死锁的产生的完整链条:

  1. 线程A进入method1,获得lock1(锁(这个资源)独占,达成互斥条件)。

  2. 线程B进入method2,获得lock2(锁(这个资源)独占,达成互斥条件)。

  3. 线程A在获得lock1的状态下试图获取lock2(线程持有资源同时请求新资源),但lock2被线程B持有(抢不到,非抢占),所以线程A等待(循环等待)。

  4. 线程B在获得lock2的状态下试图获取lock1(线程持有资源同时请求新资源),但lock1被线程A持有(抢不到,非抢占),所以线程B等待(循环等待)。

死锁达成。

单个锁也可能发生死锁问题(永久等待)
java 复制代码
class Resource {
    private final Object mLock = new Object();
    private boolean available = false;
    
    public void acquire() throws InterruptedException {
        synchronized (mLock) {
            while (!available) {
                mLock.wait();  // 释放锁,等待被唤醒
            }
            available = false;
        }
    }
    
    public void release() {
        synchronized (mLock) {
            available = true;
            mLock.notify(); // 唤醒
        }
    }
}
// 问题:如果release()从未被调用,那么acquire()线程将永远等待(排除虚假唤醒和线程中断的程序逻辑外的情况)

拓展 -- 读写锁实现(简单实现):

java 复制代码
import java.util.concurrent.locks.ReentrantReadWriteLock;

// 1. 读读不互斥(多个读可以同时进行)
// 2. 读写互斥(写时不能读,读时不能写)
// 3. 写写互斥(同一时间只有一个能写)
public class CorrectReadWriteLock {
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
    private int data;
    
    public void read() {
        readLock.lock();  // 多个读线程可以同时获取
        try {
            // 安全地读取数据
            System.out.println("Read: " + data);
        } finally {
            readLock.unlock();
        }
    }
    
    public void write(int value) {
        writeLock.lock();  // 写锁独占
        try {
            data = value;
            System.out.println("Write: " + data);
        } finally {
            writeLock.unlock();
        }
    }
}
相关推荐
我是一只小青蛙888几秒前
Java分层开发:PO、BO、DTO、VO全解析
java
步步为营DotNet1 分钟前
深度剖析.NET 中CancellationToken:精准控制异步操作的关键
java·前端·.net
a努力。6 分钟前
得物Java面试被问:B+树的分裂合并和范围查询优化
java·开发语言·后端·b树·算法·面试·职场和发展
a程序小傲7 分钟前
中国电网Java面试被问:Kafka Consumer的Rebalance机制和分区分配策略
java·服务器·开发语言·面试·职场和发展·kafka·github
lbb 小魔仙8 分钟前
从零搭建 Spring Cloud 微服务项目:注册中心 + 网关 + 配置中心全流程
java·python·spring cloud·微服务
BHXDML9 分钟前
Java 常用中间件体系化解析——从单体到分布式,从“能跑”到“可控、可扩展、可演进”
java·分布式·中间件
peachSoda79 分钟前
使用HBuilderX 自带hbuilderx-cli 自动化打包uniapp的移动端app(Android,iOS)
android·uni-app·自动化
weixin1997010801612 分钟前
安家 GO item_area - 获取地区类列表数据接口对接全攻略:从入门到精通
java·数据库·golang
码出财富16 分钟前
60万QPS下如何设计未读数系统
java·spring boot·spring cloud·java-ee