当前位置:首页 > 工具 > 正文内容

并发转账时死锁问题的解决思路

访客 工具 2026年6月14日 1

引言:粗粒度锁的性能瓶颈

在多线程环境下处理银行转账业务时,最简单的方案是使用一把全局锁将所有账户的转账操作串行化。这种方式虽然能够保证数据一致性,但会带来明显的性能问题:所有转账操作都必须排队执行,即使是完全独立的账户之间的转账也无法并行处理。例如,账户A转账给账户B的操作,与账户C转账给账户D的操作,在现实场景中完全可以并行执行,却被强制变成了串行操作,严重降低了系统吞吐量。

细粒度锁的引入与死锁风险

为了提升并发性能,我们可以采用细粒度锁策略:在转账方法内部,先锁定转出账户,再锁定转入账户,只有两个锁都获取成功时才执行转账逻辑。这种方案允许多个不相关的转账操作并行执行。

class Wallet {
    private int amount;
    
    public void sendMoney(Wallet target, int value) {
        synchronized(this) {
            synchronized(target) {
                if (this.amount > value) {
                    this.amount -= value;
                    target.amount += value;
                }
            }
        }
    }
}

然而,细粒度锁引入了新的风险:当两个线程同时进行转账操作时,可能出现循环等待的情况。线程1持有账户A的锁并等待账户B的锁,而线程2持有账户B的锁并等待账户A的锁,这种相互等待的状态就是死锁。

死锁形成的四个必要条件

死锁的产生需要满足以下四个必要条件:

  • 互斥条件:资源一次只能被一个线程持有
  • 占有并等待:线程在持有已有资源的同时,等待获取其他资源
  • 不可抢占:已经分配给线程的资源,不能被强制夺取,只能由线程主动释放
  • 循环等待:存在一种线程资源的环形等待链,每个线程都在等待下一个线程持有的资源

破坏死锁条件的三大策略

策略一:一次性申请所有资源

针对"占有并等待"条件,可以引入资源管理员角色,由它统一管理所有资源的分配。转账前向管理员申请所需的所有账户锁,只有全部申请成功才开始执行转账操作,完成后释放所有锁。

class ResourceManager {
    private final Set<Object> lockedResources = new HashSet<>();
    
    public synchronized boolean acquire(Object from, Object to) {
        if (lockedResources.contains(from) || lockedResources.contains(to)) {
            return false;
        }
        lockedResources.add(from);
        lockedResources.add(to);
        return true;
    }
    
    public synchronized void release(Object from, Object to) {
        lockedResources.remove(from);
        lockedResources.remove(to);
    }
}

class Wallet {
    private static ResourceManager manager;
    private int amount;
    
    public void sendMoney(Wallet target, int value) {
        while (!manager.acquire(this, target)) {
            // 循环等待直到获取所有锁
        }
        try {
            synchronized(this) {
                synchronized(target) {
                    if (this.amount > value) {
                        this.amount -= value;
                        target.amount += value;
                    }
                }
            }
        } finally {
            manager.release(this, target);
        }
    }
}

策略二:主动释放已占有资源

对于"不可抢占"条件,传统的synchronized关键字无法满足需求,因为线程在获取不到所需锁时会直接进入阻塞状态。Java并发包java.util.concurrent提供了可中断的锁机制,允许线程在等待过程中主动放弃已经持有的资源。

策略三:按序申请资源

针对"循环等待"条件,可以通过强制资源申请顺序来破除环路。约定所有线程必须按照统一的顺序(例如账户ID的大小顺序)来获取锁,这样就不会形成循环等待链。

class Wallet {
    private final int id;
    private int amount;
    
    public void sendMoney(Wallet target, int value) {
        Wallet first = this.id < target.id ? this : target;
        Wallet second = this.id < target.id ? target : this;
        
        synchronized(first) {
            synchronized(second) {
                if (this.amount > value) {
                    this.amount -= value;
                    target.amount += value;
                }
            }
        }
    }
}

总结

面对并发编程中的死锁问题,我们不应局限于代码层面的思考,可以借鉴现实世界的模型来寻找解决方案。资源管理员模式对应现实中的"窗口办理"机制,按序申请则类似于"排队取号"规则。理解问题的本质,选择合适的策略,才能在保证线程安全的同时最大化系统性能。

相关文章

Trojan服务器搭建与配置

一、整体架构(先对齐认知)Clash Meta (PC / iOS / Android)        ↓ TLS   Trojan Server (443)        ↓     InternetTrojan 的核心是: TLS + HTTPS 流量伪装 看起来像正常网站 非常适合...

Tailscale 的详细用法

Tailscale 是一种基于 WireGuard 协议 的 零配置 VPN(虚拟私有网络)服务,让设备之间能够 安全、加密地直接连接,就像它们在同一个本地网络一样。它的核心特点是 简单、安全、跨平台。Tailscale 非常适合 没有公网 IP、两台电脑不在同一局域网 的场景。 简单来说,Tailscale 是什么?Tailscale 是一款让你的各种设备(电脑、服务器、手机...

Clash Tun 模式 导致 爱快(iKuai SD-Wan)内网域名无法访问

一、Clash  DNS 配置dns:  enable: true  listen: 0.0.0.0:53  ipv6: true  enhanced-mode: redir-host  nameserver:    - 223.5.5.5    - 223.6.6.6iKuai 内网域名 ...

深入解析Node.js运行环境与异步I/O架构

深入解析Node.js运行环境与异步I/O架构

核心定义与价值Node.js本质上是一个JavaScript运行环境,而非编程语言或应用框架。它赋予了JavaScript脱离浏览器在服务端、命令行工具及网络应用中执行的能力。其核心意义在于:用单一语言打通前后端开发壁垒。基于事件驱动与非阻塞I/O的架构特性,Node.js在处理API网关、实时通信及微服务等I/O密集型场景时表现卓越,已成为现代后端工程的主流选择。浏览器沙箱限制1995年Java...

ADO.NET SQL参数化查询的最佳实践

在 ADO.NET 中执行 SQL 查询时,参数化查询是一种关键的安全措施和性能优化手段。它通过将 SQL 命令和用户提供的数据分开处理,有效防止了 SQL 注入攻击,并有助于数据库缓存执行计划。下面总结了几种常用的参数化查询方式。 1. 使用 SqlParameter 对象(推荐) 这是最推荐的参数化查询方式。通过显式创建 SqlParameter 对象,您可以精确控制参数的类...

基于ELK的日志集中化分析系统搭建

构建统一日志管理平台的必要性 在分布式架构中,各服务节点独立运行,日志分散存储于不同主机。传统通过命令行工具如grep、awk逐个检索日志的方式,在数据量庞大时效率极低,难以实现快速定位问题。为提升运维效率,需建立集中式日志处理体系,具备日志采集、传输、存储、分析与告警能力。 ELK技术栈核心组件解析 Elasticsearch:分布式搜索引擎,支持全文检索、实时数据分析和高可用集群部署,...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。