关于死锁问题的学习总结

死锁产生的完整链条:

复制代码
互斥 → 持有并等待 → 非抢占 → 循环等待 → 死锁
  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();
        }
    }
}
相关推荐
想学后端的前端工程师2 小时前
【Java JVM虚拟机深度解析:从原理到调优】
java·jvm·python
az3132 小时前
Spring Bean初始化机制详解
java·spring·bean·初始化
熬夜喝酒写代码2 小时前
Android Framework之编译源码
android
夜泉_ly2 小时前
期末速通 -Java程序设计基础 -理论
java·开发语言
·云扬·2 小时前
MySQL排序与分组性能优化:从原理到实践
android·mysql·性能优化
EQ-雪梨蛋花汤2 小时前
【NDK / JNI】Sceneform-EQR 集成 Filament JNI 源码:关键点与逐步操作记录
android·jni·sceneform-eqr
这是程序猿2 小时前
基于java的SpringBoot框架汽车销售系统
java·spring boot·spring·汽车·汽车销售网站
SunnyDays10112 小时前
Java 高效 TXT 转 Word 指南:单文件、批量及格式美化操作
java·txt转word·文本文件转word·文本转word
消失的旧时光-19432 小时前
从命令式跳转到声明式路由:前端、Android、Flutter 的一次统一演进
android·前端·flutter·状态模式