关于死锁问题的学习总结

死锁产生的完整链条:

复制代码
互斥 → 持有并等待 → 非抢占 → 循环等待 → 死锁
  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();
        }
    }
}
相关推荐
毕设源码-朱学姐7 小时前
【开题答辩全过程】以 基于JavaWeb的网上家具商城设计与实现为例,包含答辩的问题和答案
java
C雨后彩虹9 小时前
CAS与其他并发方案的对比及面试常见问题
java·面试·cas·同步·异步·
STCNXPARM10 小时前
Linux camera之V4L2子系统详解
android·linux·camera·v4l2架构
java1234_小锋10 小时前
Java高频面试题:SpringBoot为什么要禁止循环依赖?
java·开发语言·面试
2501_9445255410 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 账户详情页面
android·java·开发语言·前端·javascript·flutter
计算机学姐10 小时前
基于SpringBoot的电影点评交流平台【协同过滤推荐算法+数据可视化统计】
java·vue.js·spring boot·spring·信息可视化·echarts·推荐算法
Filotimo_10 小时前
Tomcat的概念
java·tomcat
索荣荣11 小时前
Java Session 全面指南:原理、应用与实践(含 Spring Boot 实战)
java·spring boot·后端
Amumu1213811 小时前
Vue Router(二)
java·前端