死锁的产生以及如何避免

打印 上一主题 下一主题

主题 1003|帖子 1003|积分 3009

一、死锁的产生原因

死锁是多个线程(或进程)因竞争资源而陷入无限等待的状态,需同时满足以下 四个须要条件

  • 互斥条件(Mutual Exclusion)

    • 资源一次只能被一个线程独占使用(如锁、文件句柄)。

  • 请求与保持(Hold and Wait)

    • 线程在持有至少一个资源的同时,请求其他线程占有的资源。

  • 不可剥夺(No Preemption)

    • 资源只能由持有者主动释放,不能被强制抢占。

  • 循环等待(Circular Wait)

    • 多个线程形成环形等待链,每个线程都在等待下一个线程释放资源。


二、典型死锁场景示例

Java 代码示例
  1. public class DeadlockDemo {
  2.     private static final Object lockA = new Object();
  3.     private static final Object lockB = new Object();
  4.     public static void main(String[] args) {
  5.         new Thread(() -> {
  6.             synchronized (lockA) {
  7.                 System.out.println("Thread1 holds lockA");
  8.                 try { Thread.sleep(100); } catch (InterruptedException e) {}
  9.                 synchronized (lockB) {  // 等待Thread2释放lockB
  10.                     System.out.println("Thread1 holds lockB");
  11.                 }
  12.             }
  13.         }).start();
  14.         new Thread(() -> {
  15.             synchronized (lockB) {
  16.                 System.out.println("Thread2 holds lockB");
  17.                 try { Thread.sleep(100); } catch (InterruptedException e) {}
  18.                 synchronized (lockA) {  // 等待Thread1释放lockA
  19.                     System.out.println("Thread2 holds lockA");
  20.                 }
  21.             }
  22.         }).start();
  23.     }
  24. }
复制代码
结果
两个线程互相等待对方释放锁,程序无限卡死。

三、死锁的检测与诊断

1. 使用工具检测死锁



  • jstack(Java自带工具):
    1. jstack <pid>  # 输出线程快照,显示死锁的线程及持有/等待的锁
    复制代码
  • VisualVMJConsole
    图形化界面查看线程状态,直接标记死锁。
2. 日记分析

若日记中线程长时间处于 BLOCKED 状态且无进展,大概发存亡锁。

四、死锁的避免战略

1. 破坏“请求与保持”条件



  • 一次性申请全部资源
    线程在开始执行前申请全部所需资源,否则不执行。
    缺点:资源利用率低,大概导致饥饿。
2. 破坏“不可剥夺”条件



  • 答应抢占资源
    若线程请求资源失败,强制释放已持有的资源(需支持回滚操纵)。
    缺点:实现复杂,适用于特定场景(如数据库事务)。
3. 破坏“循环等待”条件



  • 资源有序分配法
    为全部资源类型定义全局次序,线程按次序申请资源。
    示例
    规定必须先申请 lockA 再申请 lockB,避免交叉申请。
    1. // 修改后代码:两个线程均按 lockA → lockB 顺序申请
    2. new Thread(() -> {
    3.     synchronized (lockA) {
    4.         synchronized (lockB) { /* 逻辑 */ }
    5.     }
    6. }).start();
    7. new Thread(() -> {
    8.     synchronized (lockA) {
    9.         synchronized (lockB) { /* 逻辑 */ }
    10.     }
    11. }).start();
    复制代码
4. 使用超时机制



  • 实验获取锁时设置超时
    若在指定时间内未获得锁,放弃并释放已持有的资源,避免无限等待。
    Java实现(使用 ReentrantLock):
    1. Lock lockA = new ReentrantLock();
    2. Lock lockB = new ReentrantLock();
    3. if (lockA.tryLock(1, TimeUnit.SECONDS)) {
    4.     try {
    5.         if (lockB.tryLock(1, TimeUnit.SECONDS)) {
    6.             try { /* 逻辑 */ }
    7.             finally { lockB.unlock(); }
    8.         }
    9.     } finally { lockA.unlock(); }
    10. }
    复制代码
5. 镌汰锁的粒度



  • 缩小同步范围
    仅对须要代码加锁,镌汰锁的持有时间。
  • 使用线程安全的数据结构
    如 ConcurrentHashMap 替代 synchronized + HashMap。

五、最佳实践总结

战略适用场景长处缺点资源有序分配多锁交叉申请场景简朴有用,预防循环等待需全局统一次序,大概限制灵活性超时机制高并发、答应重试的场景避免无限等待,提升体系结实性需处理超时重试逻辑无锁编程(CAS、原子类)低竞争、简朴操纵场景高性能,无死锁风险复杂逻辑实现困难事务回滚数据库、支持回滚的操纵保证数据同等性实现资本高
六、总结

死锁的避免需联合业务场景选择符合的战略:


  • 关键体系(如金融交易):优先使用资源有序分配和超时机制。
  • 高并发体系:镌汰锁粒度,采用无锁数据结构。
  • 复杂事务:联合事务管理和回滚机制。
通过代码规范、工具检测和设计优化,可显著降低死锁发生概率。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

卖不甜枣

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表