多线程基础系列-线程死锁

文章目录

  • 多线程基础系列-线程死锁
    • [1. 什么是线程死锁?](#1. 什么是线程死锁?)
    • [2. 如何检测线程死锁?](#2. 如何检测线程死锁?)
    • [3. 如何预防和避免线程死锁?](#3. 如何预防和避免线程死锁?)

多线程基础系列-线程死锁

1. 什么是线程死锁?

  • 定义:两个或多个线程互相持有对方需要的资源并永久等待,导致线程都无法继续执行。
  • 必要条件(同时满足才会发生):
    • 互斥:资源一次只能被一个线程占用。
    • 请求并保持:持有资源的同时再请求新资源。
    • 不可剥夺:已获得的资源不能被强制抢占。
    • 循环等待:形成资源等待环路。
  • 一个经典示例:线程 A 持有锁 L1 等待 L2;线程 B 持有锁 L2 等待 L1,循环等待 → 死锁。

    示例代码(故意制造死锁):
java 复制代码
public class DeadlockDemo {
    private static final Object L1 = new Object();
    private static final Object L2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (L1) {
                System.out.println("T1 get L1");
                sleep(100);
                System.out.println("T1 waiting get L2");
                synchronized (L2) {
                    System.out.println("T1 acquired L1 and L2");
                }
            }
        }, "T1");

        Thread t2 = new Thread(() -> {
            synchronized (L2) {
                System.out.println("T2 get L2");
                sleep(100);
                System.out.println("T2 waiting get L1");
                synchronized (L1) {
                    System.out.println("T2 acquired L2 and L1");
                }
            }
        }, "T2");

        t1.start();
        t2.start();
    }

    private static void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException ignored) {}
    }
}

2. 如何检测线程死锁?

  • 线程 Dump(推荐首选)
    • jstack <pid>:直接输出线程栈,底部会有 "Found one Java-level deadlock"。
    • jcmd <pid> Thread.print:与 jstack 类似,可多次采样比对。
    • jmap -histo:结合对象分布,辅助判断锁竞争热点。
  • 可视化工具
    • JConsole / VisualVM:线程页签可直接标红死锁线程。

这里以 JConsole 工具为例进行演示

  • 第一步运行我们编写的死锁示例
  • 第二步我们要找到 JDK 的 bin 目录,找到 jconsole 并双击打开,并选择我死锁demo的进程
  • 第三步:[线程]一栏点击"检测死锁"
  • 最后就可以看到死锁线程的相关说明

3. 如何预防和避免线程死锁?

  • 统一加锁顺序
    • 约定所有线程获取多把锁的顺序,避免循环等待(如总是先锁 L1 再锁 L2)。
  • 减少锁粒度与持有时间
    • 缩小同步块范围;避免在持锁期间执行 IO/远程调用。
  • 尽量避免嵌套锁
    • 能拆开的锁拆开;必要时将逻辑重构为无嵌套的步骤。
  • 使用可中断/可超时锁
    • ReentrantLock.tryLock(timeout, unit):拿不到锁及时放弃,避免永久等待。
java 复制代码
ReentrantLock lockA = new ReentrantLock();
if (lockA.tryLock(200, TimeUnit.MILLISECONDS)) {
    try {
        // 临界区
    } finally {
        lockA.unlock();
    }
} else {
    // 降级或重试逻辑
}
  • 使用更高层并发工具
    • java.util.concurrent 中的 ConcurrentHashMapBlockingQueueSemaphoreStampedLock 等,减少显式锁。
  • 不可变与无共享设计
    • 尽量使用不可变对象或线程封闭(Thread confinement),减少共享状态。
  • 分离职责与锁分段
    • 按领域拆分锁(lock striping),避免大锁串行化。
  • 定期监控与压测
    • 在预生产或压测环境跑热点场景,定期采集线程 Dump 进行巡检。
相关推荐
bluetata2 小时前
在 Spring Boot 中使用 Amazon Textract 从图像中提取文本
java·spring boot·后端
黎雁·泠崖2 小时前
Java底层探秘入门:从源码到字节码!方法调用的中间形态全解析
java·开发语言
we1less2 小时前
[audio] AudioTrack (六) 共享内存使用分析
java·开发语言
CYTElena2 小时前
关于JAVA异常的笔记
java·开发语言·笔记·语言基础
YIN_尹2 小时前
【C++11】lambda表达式(匿名函数)
java·c++·windows
猴子年华、2 小时前
【每日一技】:SQL 常用函数实战速查表(函数 + 场景版)
java·数据库·sql·mysql
码农水水3 小时前
京东Java面试被问:系统限流的实现方式
java·开发语言·面试
宁晓3 小时前
单表配置多级类型,按名称模糊筛选
java·后端
Yu_iChan3 小时前
Day03 公共字段填充与菜品管理
java·开发语言