0

0

如何避免多线程交换操作中的死锁:基于锁顺序的确定性解决方案

霞舞

霞舞

发布时间:2026-02-17 17:14:08

|

558人浏览过

|

来源于php中文网

原创

如何避免多线程交换操作中的死锁:基于锁顺序的确定性解决方案

本文探讨在多线程环境下安全实现对象间值交换时,为何“反复尝试获取锁”(spin-locking)不是推荐方案,并系统介绍一种更可靠、可预测的防死锁策略——通过全局一致的锁获取顺序(如基于唯一id排序)来彻底消除死锁风险。

本文探讨在多线程环境下安全实现对象间值交换时,为何“反复尝试获取锁”(spin-locking)不是推荐方案,并系统介绍一种更可靠、可预测的防死锁策略——通过全局一致的锁获取顺序(如基于唯一id排序)来彻底消除死锁风险。

在并发编程中,swapValue(Data other) 这类跨对象操作极易引发死锁。原始代码中混合使用 synchronized 方法与显式 ReentrantLock,且未约定锁获取顺序,导致 a.swapValue(b) 与 b.swapValue(a) 可能以相反顺序竞争 a.lock/b.lock 和 a/b 的内置监视器锁——形成经典的循环等待,即死锁根源。

而您提出的“轮询重试”方案(while(!other.lock.tryLock()) { lock.unlock(); lock.lock(); })虽能偶然避免死锁,但存在严重缺陷:

  • 非确定性:依赖线程调度时机和锁释放/获取的竞争窗口,行为不可预测;
  • 资源浪费:空转(busy-waiting)消耗CPU,尤其在高争用场景下性能急剧下降;
  • 违反锁设计原则:tryLock() 本意是支持超时或中断处理,而非构建自旋逻辑;
  • 隐患叠加:仍与 synchronized 方法共存,实际锁粒度混乱(内置锁 + 显式锁),加剧不确定性。

✅ 推荐方案:全局一致的锁顺序(Lock Ordering)

核心思想:对任意两个 Data 实例,始终以相同、可比、不可变的顺序获取锁。这样可打破循环等待条件,从根源上杜绝死锁。

Toolify.ai
Toolify.ai

Toolify.ai是一个专门收集、评测AI工具和服务的网址导航站

下载

最常用且健壮的做法是引入一个全局唯一、不可变的标识符,并在加锁前按其自然序(如升序)统一排序:

public class Data {
    private final long id; // 全局唯一ID,构造时生成
    private volatile long value;
    private final ReentrantLock lock = new ReentrantLock();

    public Data(long value) {
        this.id = generateUniqueId(); // 见下方实现
        this.value = value;
    }

    // 推荐:使用 AtomicLong 保证全局唯一性(优于 System.identityHashCode)
    private static final AtomicLong NEXT_ID = new AtomicLong(0);
    private static long generateUniqueId() {
        return NEXT_ID.incrementAndGet();
    }

    public long getValue() {
        lock.lock();
        try {
            return value;
        } finally {
            lock.unlock();
        }
    }

    public void setValue(long value) {
        lock.lock();
        try {
            this.value = value;
        } finally {
            lock.unlock();
        }
    }

    public void swapValue(Data other) {
        // 关键:按 id 升序决定加锁顺序,确保全局一致
        Data first = (this.id <= other.id) ? this : other;
        Data second = (this.id <= other.id) ? other : this;

        first.lock.lock();
        try {
            second.lock.lock();
            try {
                // 安全执行交换(此时两把锁均已持有)
                long temp = this.getValue();      // 注意:此处调用已加锁的 getValue()
                long newValue = other.getValue();
                this.setValue(newValue);
                other.setValue(temp);
            } finally {
                second.lock.unlock(); // 逆序释放:先 second,后 first
            }
        } finally {
            first.lock.unlock();
        }
    }
}

⚠️ 关键注意事项

  • ID 必须真正唯一:System.identityHashCode() 仅是哈希值,存在碰撞可能,不可用于锁排序。应使用 AtomicLong 自增、UUID(字符串比较开销大)、或数据库序列等强唯一机制。
  • 锁获取顺序严格一致:永远先锁 min(id1, id2),再锁 max(id1, id2)。即使 swapValue 被反向调用(b.swapValue(a)),计算出的 first/second 仍相同。
  • 释放顺序建议逆序:虽然本例中释放顺序不影响死锁,但遵循“后锁先释”(LIFO)是通用最佳实践,有助于减少锁持有时间并提升可维护性。
  • 移除 synchronized 混用:所有临界区统一通过 ReentrantLock 管理,避免内置锁与显式锁语义混淆。若需 synchronized 兼容性,应彻底重构为纯显式锁或纯内置锁方案。
  • 考虑更高阶抽象:对于复杂协作场景,可进一步采用 java.util.concurrent.locks.StampedLock(读写优化)或 java.util.concurrent.locks.ReadWriteLock,但锁顺序原则依然适用。

总结

“反复尝试获取锁”是一种脆弱、低效、反模式的死锁规避手段。真正的工程实践应追求确定性可证明安全性。通过为共享对象分配唯一ID并强制统一锁获取顺序,我们能以简洁、高效、无竞争的方式根除死锁风险。该模式已被广泛应用于数据库事务、分布式锁协调及高性能并发容器(如 ConcurrentHashMap 分段锁)中,是 Java 并发开发中值得牢固掌握的核心范式。

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
什么是分布式
什么是分布式

分布式是一种计算和数据处理的方式,将计算任务或数据分散到多个计算机或节点中进行处理。本专题为大家提供分布式相关的文章、下载、课程内容,供大家免费下载体验。

392

2023.08.11

分布式和微服务的区别
分布式和微服务的区别

分布式和微服务的区别在定义和概念、设计思想、粒度和复杂性、服务边界和自治性、技术栈和部署方式等。本专题为大家提供分布式和微服务相关的文章、下载、课程内容,供大家免费下载体验。

246

2023.10.07

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

103

2023.09.25

mysql标识符无效错误怎么解决
mysql标识符无效错误怎么解决

mysql标识符无效错误的解决办法:1、检查标识符是否被其他表或数据库使用;2、检查标识符是否包含特殊字符;3、使用引号包裹标识符;4、使用反引号包裹标识符;5、检查MySQL的配置文件等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

195

2023.12.04

Python标识符有哪些
Python标识符有哪些

Python标识符有变量标识符、函数标识符、类标识符、模块标识符、下划线开头的标识符、双下划线开头、双下划线结尾的标识符、整型标识符、浮点型标识符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

304

2024.02.23

java标识符合集
java标识符合集

本专题整合了java标识符相关内容,想了解更多详细内容,请阅读下面的文章。

272

2025.06.11

c++标识符介绍
c++标识符介绍

本专题整合了c++标识符相关内容,阅读专题下面的文章了解更多详细内容。

138

2025.08.07

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

553

2023.08.03

pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法
pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法

本专题系统整理pixiv网页版官网入口及登录访问方式,涵盖官网登录页面直达路径、在线阅读入口及快速进入方法说明,帮助用户高效找到pixiv官方网站,实现便捷、安全的网页端浏览与账号登录体验。

283

2026.02.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号