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

文章目录

  • 多线程基础系列-线程死锁
    • [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 进行巡检。
相关推荐
郝学胜-神的一滴1 分钟前
干货版《算法导论》07:递归视角下的选择排序与归并排序
java·数据结构·c++·python·程序人生·算法·排序算法
掉鱼的猫14 分钟前
Solon Server 启动模式深度解析:从 0.3MB 内核到 10+ Server 插件
java·http
shehuiyuelaiyuehao15 分钟前
多线程入门
java·python·算法
星夜夏空9915 分钟前
FreeRTOS学习(7)——任务列表
java·前端·学习
han_hanker21 分钟前
BeanUtils.copyProperties 和序列化的问题
java·开发语言·spring boot
野生技术架构师24 分钟前
牛客网2026互联网大厂Java面试题汇总,附官方级答案解析
java·开发语言
西凉的悲伤1 小时前
Spring Boot 中 @Async(value = “alertThreadPool“) 是什么?为什么企业项目喜欢自定义线程池?
spring boot·多线程·async·异步
AZaLEan__1 小时前
多源 BFS
java·开发语言·算法
程序员卷卷狗1 小时前
Java转Go面试速记:Go基础22问,一篇理清高频易错点一篇理清高频易错点
java·面试·golang
zzzzz3691 小时前
快速搭建SpringAi项目 集成智能问答,RAG,FUINCTION_CALLING等功能
java·ai编程