Java多线程中的死锁问题与避免方法

发布时间 - 2026-01-08 00:00:00    点击率:
死锁典型模式是多线程以不同顺序获取同一组锁,如线程A先锁accountA再锁accountB,线程B反之;常用tryLock()超时避免、统一锁顺序预防、jstack和ThreadMXBean排查。

死锁发生的典型代码模式

Java中死锁最常出现在多个线程以不同顺序获取同一组 Object 锁(或 synchronized 块)时。比如线程 A 先锁 accountA 再尝试锁 accountB,而线程 B 正好相反——这种交叉加锁是死锁的直接诱因。

常见错误现象包括:程序卡住无响应、CPU 占用率低、线程状态长期为 BLOCKED(可通过 jstack 查看线程堆栈,若看到两个以上线程互相等待对方持有的锁,基本可确认)。

  • 只在同步块内调用可能阻塞或依赖其他锁的方法,极易引入隐式锁依赖
  • 使用 ReentrantLock 时调用 lock() 而非 tryLock(long, TimeUnit),失去超时控制能力
  • 数据库事务 + JVM 锁混合使用时,JDBC 连接持有和对象锁顺序不一致,也会触发跨层死锁

如何用 tryLock() 主动规避死锁

ReentrantLock.tryLock(long, TimeUnit) 是最实用的防御手段——它允许你设定等待上限,失败后可释放已持锁并重试或回退,打破循环等待条件。

ReentrantLock lockA = new ReentrantLock();
ReentrantLock lockB = new ReentrantLock();

void transfer(Account from, Account to, BigDecimal amount) {
    while (true) {
        if (lockA.tryLock(100, TimeUnit.MILLISECONDS) &&
            lockB.tryLock(100, TimeUnit.MILLISECONDS)) {
            try {
                from.withdraw(amount);
                to.deposit(amount);
                return;
            } finally {
                lockA.unlock();
                lockB.unlock();
            }
        } else {
            // 至少一个锁没拿到,先释放已获得的锁,避免僵持
            if (lockA.isHeldByCurrentThread()) lockA.unlock();
            if (lockB.isHeldByCurrentThread()) lockB.unlock();
            Thread.sleep(10); // 避免忙等
        }
    }
}

注意:tryLock() 不支持公平锁的“排队语义”,且必须严格配对 unlock(),否则会导致锁泄漏。

统一锁顺序是成本最低的预防方式

如果所有线程都按相同规则获取锁(例如总是先锁 ID 小的对象),就能从根源上消除循环等待。这不需要额外 API,只需约定和校验。

使用场景:账户转账、资源池分配、树形结构遍历等存在天然可比较标识的场景。

  • 对锁对象实现 Comparable,或提取唯一可比字段(如 account.getId()
  • 加锁前先排序:List locks = Arrays.asList(obj1, obj2); locks.sort(Comparator.comparing(System::identityHashCode)); —— 用 System.identityHashCode() 是安全兜底,但不保证跨 JVM 一致,仅限单 JVM 内有效
  • 避免在锁内做任何可能引发新锁竞争的操作(如调用外部服务、访问 synchronized 集合)

排查与监控不能只靠日志

死锁往往在压测或上线后才暴露,仅靠业务日志很难定位。必须结合 JVM 自带机制和轻量级埋点。

关键操作:

  • 启动参数加入 -XX:+PrintConcurrentLocks -XX:+PrintGCDetails,配合 jstack -l 可输出显式锁持有关系
  • 定期调用 java.lang.management.ThreadMXBean.findDeadlockedThreads() 做主动探测(建议封装为健康检查端点)
  • 对高风险同步块添加计时日志:long start = System.nanoTime(); ... log.warn("sync block took {}ms", (System.nanoTime()-start)/1_000_000);,持续偏高的耗时是潜在死锁前兆

真正难处理的是“锁链过长”:不是两把锁互等,而是 A→B→C→D→A 形成环路,这种靠人工 review 几乎不可控,必须依赖工具链自动分析锁获取路径。


# java  # js  # 工具  #   # ai  # java多线程 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 最好的网站制作公司,网购哪个网站口碑最好,推荐几个?谢谢?  如何在阿里云完成域名注册与建站?  如何在IIS中配置站点IP、端口及主机头?  Python自然语言搜索引擎项目教程_倒排索引查询优化案例  Laravel定时任务怎么设置_Laravel Crontab调度器配置  中国移动官方网站首页入口 中国移动官网网页登录  韩国网站服务器搭建指南:VPS选购、域名解析与DNS配置推荐  Laravel如何配置Horizon来管理队列?(安装和使用)  Bootstrap整体框架之JavaScript插件架构  Laravel Facade的原理是什么_深入理解Laravel门面及其工作机制  标准网站视频模板制作软件,现在有哪个网站的视频编辑素材最齐全的,背景音乐、音效等?  如何在IIS管理器中快速创建并配置网站?  php结合redis实现高并发下的抢购、秒杀功能的实例  Laravel如何使用Eloquent ORM进行数据库操作?(CRUD示例)  简单实现Android验证码  在centOS 7安装mysql 5.7的详细教程  Laravel事件监听器怎么写_Laravel Event和Listener使用教程  家族网站制作贴纸教程视频,用豆子做粘帖画怎么制作?  微信小程序 input输入框控件详解及实例(多种示例)  青岛网站建设如何选择本地服务器?  香港服务器WordPress建站指南:SEO优化与高效部署策略  佐糖AI抠图怎样调整抠图精度_佐糖AI精度调整与放大细化操作【攻略】  高防服务器租用如何选择配置与防御等级?  Laravel怎么配置.env环境变量_Laravel生产环境敏感数据保护与读取【方法】  Laravel队列任务超时怎么办_Laravel Queue Timeout设置详解  Laravel怎么使用artisan命令缓存配置和视图  laravel怎么在请求结束后执行任务(Terminable Middleware)_laravel Terminable Middleware请求结束任务执行方法  Laravel如何配置和使用缓存?(Redis代码示例)  Linux虚拟化技术教程_KVMQEMU虚拟机安装与调优  如何利用DOS批处理实现定时关机操作详解  简单实现Android文件上传  制作ppt免费网站有哪些,有哪些比较好的ppt模板下载网站?  Linux系统命令中tree命令详解  ,网页ppt怎么弄成自己的ppt?  微信公众帐号开发教程之图文消息全攻略  Laravel如何实现事件和监听器?(Event & Listener实战)  java获取注册ip实例  Gemini手机端怎么发图片_Gemini手机端发图方法【步骤】  Laravel如何自定义分页视图?(Pagination示例)  MySQL查询结果复制到新表的方法(更新、插入)  如何快速配置高效服务器建站软件?  如何在IIS中新建站点并配置端口与物理路径?  iOS验证手机号的正则表达式  Laravel如何连接多个数据库_Laravel多数据库连接配置与切换教程  如何在万网ECS上快速搭建专属网站?  Laravel怎么实现支付功能_Laravel集成支付宝微信支付  如何快速查询网站的真实建站时间?  如何快速生成专业多端适配建站电话?  Thinkphp 中 distinct 的用法解析  如何用腾讯建站主机快速创建免费网站?