ReentrantLock 中 lock 与 tryLock 核心区别

在 Java 显式锁体系中,ReentrantLock 是替代 synchronized 的核心灵活锁工具,而 lock()tryLock() 是其最常用的两个加锁方法。很多开发者容易混淆两者的使用边界,甚至乱用导致死锁、线程阻塞雪崩、业务逻辑异常。

先给大家建立一个核心认知:两者都是 ReentrantLock 的独占加锁方法,本质差异只有一个:拿不到锁的时候,要不要死等lock() 是拿不到就一直阻塞等待,直到拿到锁;tryLock() 是拿不到就不排队,直接返回(或等一会儿就走),线程可以转去做其他事。

一、lock ():阻塞式独占加锁

1. 功能

它是排队死等的阻塞锁。锁被占用时,当前线程必须进入等待队列排队,一直等到前面的线程释放锁、自己抢到锁为止,中途不能退出、不能去做别的事,只要不发生死锁,最终一定能拿到锁。

生活化例子:火车站人工售票窗口只有一个,你必须排在队伍里死等,前面的人办完才轮到你;中途离开就得重新排队,只要你一直排,最终肯定能买到票,但等待期间啥也干不了。

2. 底层原理

lock() 完全基于 AQS(AbstractQueuedSynchronizer)独占模式 实现,是标准的阻塞式独占锁逻辑:

  1. 线程调用 lock() 时,首先尝试用 CAS 原子操作把 AQS 的 state 变量从 0 修改为 1;如果修改成功,就把当前线程标记为锁的独占持有线程,成功拿锁,直接执行业务逻辑。

  2. 如果 CAS 失败(锁已被其他线程占用),就把当前线程封装为等待节点,加入 AQS 双向等待队列的尾部。

  3. 入队后调用 LockSupport.park() 将当前线程阻塞挂起,进入休眠等待状态,释放 CPU 资源,直到被前驱节点释放锁时唤醒。

  4. 被唤醒后再次尝试 CAS 抢锁,抢到就继续执行业务,抢不到就再次进入阻塞等待,循环往复直到拿到锁。

核心细节:普通 lock()不可中断 的。一旦进入等待队列,即使外部调用 interrupt() 中断线程,它也不会退出等待,只会继续阻塞抢锁,直到成功拿到锁。如果需要可中断的阻塞等待,要使用专门的 lockInterruptibly() 方法。

3. 优缺点

|-------------------------------|--------------------------------|
| 优点 | 缺点 |
| 编程模型简单,无需处理抢锁失败逻辑,代码量少 | 线程阻塞挂起,等待期间无法执行其他任务,CPU 资源利用率低 |
| 无死锁前提下最终一定能获取锁,适合必须执行的核心任务 | 无限等待机制容易引发死锁,一个锁卡住就会导致整条链路阻塞 |
| 基于 AQS 原生实现,性能稳定,锁竞争激烈时公平性有保障 | 响应不灵活,无法设置超时时间,不支持快速失败 |
| 支持公平 / 非公平模式切换,适配不同调度需求 | 普通 lock 不可中断,无法主动取消等待,异常场景容错差 |

4. 选型思考

适合锁竞争平缓、业务必须执行、流程简单的强一致同步场景,比如核心数据串行化更新、状态机流转、独占资源操作。

选型原则:业务必须执行、对延迟不敏感、锁冲突概率低的场景,优先选 lock,简单稳定、维护成本低;高并发、易死锁场景谨慎使用。

二、tryLock ():非阻塞尝试加锁

1. 功能

它是尝试抢锁的非阻塞锁。线程去尝试拿锁,拿到了就执行,拿不到就直接返回失败,不排队、不阻塞,线程可以立刻去做其他任务;也可以设置最长等待时间,超时还拿不到就放弃。获取失败可以采用自旋重试的方式多次尝试获取锁信息,等待期间也可以做其他事情,就是长时间的重试会降低cpu性能,需要取舍。

生活化例子:去便利店借共享充电宝,你过去看一眼,有空的就直接拿走用;没有空的,你直接就走,去下一家店找,不站在原地死等。也可以约定等 5 分钟,还没有就直接离开。

2. 底层原理

tryLock() 同样基于 AQS 实现,但走的是快速尝试 + 可选超时的非阻塞逻辑,分为两种形态:

  1. 无参 tryLock () :只做一次 CAS 抢锁尝试,成功就返回 true,失败直接返回 false不进入等待队列、不阻塞线程,立刻把控制权交还给业务代码,由开发者决定失败后是重试、跳过还是降级。

  2. 带超时 tryLock (long time, TimeUnit unit) :先做一次 CAS 尝试,失败的话会在指定的超时时间内进入有限等待;等待期间线程会被挂起,但超时时间一到立刻返回 false,不再继续等;并且等待过程中可以响应线程中断,提前退出等待。

核心细节:tryLock 只做 "尝试性获取",不保证一定能拿到锁,所有业务逻辑必须先判断返回值,确认拿到锁之后才能操作共享资源,这是新手最容易踩的坑。

3. 优缺点

|-----------------------------|------------------------------|
| 优点 | 缺点 |
| 非阻塞设计,抢锁失败线程可以执行其他任务,资源利用率高 | 不保证一定能拿到锁,需要编写失败处理逻辑,代码复杂度更高 |
| 支持超时控制,从根源避免无限等待死锁,容错性强 | 锁竞争激烈时成功率低,频繁重试会消耗额外 CPU 资源 |
| 支持线程中断,灵活可控,可随时取消抢锁操作 | 无参模式不保证公平性,长等待场景下可能出现线程饥饿 |
| 支持快速失败,高并发场景下避免服务阻塞雪崩 | 滥用重试逻辑容易导致活锁,CPU 占用飙升 |

4. 选型思考

适合高并发、非核心业务、死锁规避、需要超时控制的场景,比如分布式锁尝试抢占、非强制资源操作、热点接口限流、多锁顺序死锁规避。

选型原则:可跳过、可降级、可重试的业务优先用 tryLock,用灵活性换取系统稳定性;必须执行的核心业务谨慎使用,避免抢锁失败导致业务异常。

三、核心差异对照表

|----------|--------------------|------------------------------|
| 对比维度 | lock() | tryLock() |
| 加锁模式 | 阻塞式独占加锁 | 非阻塞尝试加锁 |
| 阻塞特性 | 拿不到锁永久阻塞,直到成功 | 拿不到锁立即返回 / 超时返回,不阻塞 |
| 返回值 | 无返回值,执行即代表拿锁成功 | boolean 返回值,true 成功、false 失败 |
| 等待机制 | 进入 AQS 等待队列,永久等待唤醒 | 无参不排队;带参仅等待指定时长 |
| 可中断性 | 普通 lock 不可中断,永久等待 | 带超时版本支持中断,可提前退出 |
| 死锁风险 | 高,无限等待容易引发连锁死锁 | 低,超时机制从根源避免无限等待 |
| 编程复杂度 | 低,无需处理失败逻辑 | 高,必须判断返回值、处理失败场景 |
| CPU 资源占用 | 阻塞时释放 CPU,资源占用低 | 失败后线程继续运行,频繁重试会占用高 CPU |
| 公平性 | 支持公平 / 非公平模式,公平性可控 | 无参模式无公平性,易出现线程饥饿 |
| 核心场景 | 必须执行的核心同步任务 | 可降级、可重试、防死锁的高并发场景 |

四、选型口诀与避坑指南

1. 选型口诀

  • 核心必做、锁竞争低 → 选 lock,简单稳定

  • 防死锁、要超时控制 → 选 tryLock,灵活容错

  • 非核心、可跳过任务 → 选 tryLock,失败直接降级

  • 多锁顺序不确定场景 → 选 tryLock,从根源规避死锁

2. 高频生产避坑点

  • 禁止 tryLock 不判断返回值:直接执行业务逻辑,导致没拿到锁也修改共享数据,引发并发脏数据。

  • 禁止锁释放不在 finally 块:无论是 lock 还是 tryLock,释放锁必须写在 finally 中,防止异常导致锁永久泄露。

  • 禁止死循环重试 tryLock:不加退避策略循环重试抢锁,导致 CPU 被打满,服务雪崩。

  • 禁止用 lock 处理非核心任务:大量线程阻塞等待非关键资源,引发线程耗尽、服务不可用。

五、总结

locktryLock 没有绝对的好坏之分,只有场景适配之别。lock 是稳妥的传统同步方案,简单、稳定、可预测,适合核心强一致场景;tryLock 是灵活的高并发方案,非阻塞、可超时、防死锁,适合高可用、高容错的分布式场景。

架构选型的核心原则永远是:业务必要性优先,稳定性与灵活性做平衡。必须执行的核心任务求稳用 lock,高并发容错场景求活用 tryLock,匹配场景才是最优解。

相关推荐
.Hypocritical.16 分钟前
Tomcat本地部署+远程服务器部署超详细教程
java·服务器·tomcat
2601_9620629423 分钟前
Spring Boot入门——Spring Boot项目的创建
java·数据库·spring boot
一嘴一个橘子1 小时前
springmvc 全局异常处理【补充】
java
Wang's Blog1 小时前
Vibe Coding一人即团队系列35: 基于Claude Code的Spring Boot项目初始化实践
java·spring boot·后端
Dovis(誓平步青云)2 小时前
拍视频前先把镜头想清楚:做一个分镜取景辅助器
android·java·服务器·javascript·人工智能
AI人工智能+电脑小能手2 小时前
大白话说Java设计模式-45-解释器模式(源码剖析篇)
java·设计模式·解释器模式·源码分析·pattern·spel·javacc
吴声子夜歌2 小时前
Java——开发中通用的方法和准则(二)
java·开发语言·php
袁震2 小时前
HarmonyOS 应用包体积优化与上架自检实战:从 76.2MB 到 3.6MB
java·华为·性能优化·harmonyos
zcmodeltech2 小时前
反应装置模型控制系统设计与实现:多设备协同联动方案
java·网络·数据库·stm32·嵌入式硬件·能源·制造