Java并发编程:乐观锁、悲观锁、公平锁、非公平锁

在Java并发编程中,理解不同类型的锁及其适用场景至关重要。下面这个表格汇总了乐观锁、悲观锁、公平锁和非公平锁的核心特性和典型实现,希望能帮助你快速建立整体概念。

锁类型 核心思想/假设 典型实现举例
​乐观锁​ 假设并发冲突概率很小,先操作数据,提交时再检测冲突。 1. ​版本号/时间戳机制 ​ (常用于数据库) 2. ​原子类 ​ (java.util.concurrent.atomic包下的 AtomicInteger等,基于CAS)
​悲观锁​ 假设并发冲突概率很高,访问数据前先加锁,确保操作期间的排他性。 1. ​synchronized关键字 ​ 2. ​ReentrantLock等 Lock接口实现类 ​ 3. ​数据库悲观锁 ​ (如 SELECT ... FOR UPDATE)
​公平锁​ 按照线程请求锁的先后顺序来分配锁,保证先到先得,避免线程"饥饿"。 1. ​ReentrantLock(true) ​ (构造时传入 true) 2. ​Semaphore等并发工具也可配置为公平模式​
​非公平锁​ 允许"插队",线程获取锁的顺序不一定与请求顺序一致,可能提高吞吐量。 1. ​synchronized关键字​ (内置实现即为非公平锁) 2. ​ReentrantLock()/ ReentrantLock(false) ​ (默认或显式设置为非公平)

💡 项目实战与选择策略

了解概念和实现后,关键在于如何在实战中做出正确选择。

🔐 乐观锁 vs 悲观锁

选择取决于并发冲突的概率 和应用场景。

  • ​乐观锁适用场景 ​:​读多写少,冲突概率低的场景,如:

    • 商品信息更新:多个管理员可能同时修改商品描述,但冲突概率不高。
    • 用户积分微调:并发修改次数相对较少。
    • 数据库并发更新:通过版本号字段控制。
    ini 复制代码
    // 示例:基于版本号的乐观锁数据库更新模式
    String sql = "UPDATE products SET name = ?, price = ?, version = version + 1 WHERE id = ? AND version = ?";
    int affectedRows = jdbcTemplate.update(sql, newName, newPrice, productId, currentVersion);
    if (affectedRows == 0) {
        // 处理更新失败(版本号不匹配),通常重试或抛出异常
        throw new OptimisticLockingFailureException("数据已被其他事务修改");
    }
  • ​悲观锁适用场景 ​:​写多读少 ,冲突概率高,或需要强一致性的场景,如:

    • 银行转账:涉及关键资金,必须保证绝对准确。
    • 库存扣减/秒杀:高并发下防止超卖。
    • 数据库事务中锁定关键记录。
    csharp 复制代码
    // 示例:使用 ReentrantLock 的悲观锁
    public class BankAccountService {
        private final ReentrantLock lock = new ReentrantLock();
        public void transfer(Account from, Account to, BigDecimal amount) {
            lock.lock(); // 获取锁
            try {
                // 执行转账业务逻辑
                if (from.getBalance().compareTo(amount) >= 0) {
                    from.debit(amount);
                    to.credit(amount);
                }
            } finally {
                lock.unlock(); // 务必在finally块中释放锁
            }
        }
    }

⚖️ 公平锁 vs 非公平锁

选择取决于对线程执行顺序(公平性)的要求 和系统吞吐量的权衡。

  • ​公平锁适用场景 ​:需要严格保证线程不会"饥饿"​,对公平性要求高于性能的场景,如:

    • 订票系统:保证先到先得。
    • 任务调度:需要按提交顺序执行任务。
    java 复制代码
    // 示例:创建公平锁
    private final ReentrantLock fairLock = new ReentrantLock(true); // 传入true创建公平锁
  • ​非公平锁适用场景 ​:​绝大多数情况 ,为了追求更高的系统吞吐量,如:

    • 大部分业务逻辑处理。
    • 高性能服务器端应用。
    java 复制代码
    // 示例:创建非公平锁(默认行为)
    private final ReentrantLock nonFairLock = new ReentrantLock(); // 默认即为非公平锁
    // 或显式声明
    // private final ReentrantLock nonFairLock = new ReentrantLock(false);

🚀 锁性能优化实战要点

  1. 减小锁粒度:只锁必要的代码块,而不是整个方法。
  2. 缩短锁持有时间:避免在锁内执行IO、网络请求等耗时操作。
  3. 锁分离 :读多写少时,优先考虑 ReadWriteLock或 StampedLock。
  4. 无锁化设计 :对于简单的状态标记、计数器,优先使用 Atomic原子类。
  5. 避免锁嵌套:容易导致死锁。
相关推荐
子兮曰4 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰4 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
爱勇宝4 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码4 天前
别再前后端各写一套表单校验了
java·后端
大勇前进4 天前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
yuzhi_liu4 天前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile4 天前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go
大白804 天前
PHP 内存溢出排查思路:看懂报错日志,精准定位问题
后端
二月龙4 天前
PHP 接口返回统一响应封装,让前后端对接更省心
后端
盖伦发发4 天前
软件工程SOLID 五大设计原则
后端·软件工程