Java synchronized 与 ReentrantLock 到底怎么选:可中断、tryLock 超时、公平锁与 Condition 精准唤醒

发布时间:2026/11/5 14:01:40

Java synchronized 与 ReentrantLock 到底怎么选:可中断、tryLock 超时、公平锁与 Condition 精准唤醒 Java synchronized 与 ReentrantLock 到底怎么选:可中断、tryLock 超时、公平锁与 Condition 精准唤醒写并发代码加锁,新手清一色用synchronized——简单,一个关键字搞定。但工作几年后你会遇到一堆synchronized干不了的场景:线程卡在锁上想中断它、抢不到锁不想干等而是超时放弃、要区分「读多写少」、要精确唤醒某一类等待线程。这时候ReentrantLock就该上场了。这篇文章不堆 API,而是从「synchronized具体卡在哪」出发,一个个用ReentrantLock的能力去补,最后给一条清晰的选型结论。先把 synchronized 的定位说清楚synchronized不是过时的东西,恰恰相反,大多数场景它就是最优解。原因:publicclassCounter{privateintcount;// 方法级:锁的是 thispublicsynchronizedvoidinc(){count;}// 代码块级:锁的是指定对象,粒度更细privatefinalObjectlocknewObject();publicvoidincBlock(){synchronized(lock){count;}}}它的优点是:JVM 内建、无需手动释放(异常了也自动解锁)、JIT 对它有偏向锁/轻量级锁等优化。只要你的需求是「简单互斥」,别折腾,用synchronized。它的局限在于「不可控」:一旦一个线程没抢到锁,就只能死等,你没有任何手段干预。下面四个场景就是它的天花板。局限一:抢不到锁想放弃 → tryLocksynchronized抢锁是「要么拿到、要么阻塞」,没有第三条路。但现实里经常需要「试着拿一下,拿不到就干别的,别在这耗着」。比如一个定时任务,上一轮还没跑完就跳过本轮,而不是排队堆积。ReentrantLock.tryLock()提供了这个能力:importjava.util.concurrent.locks.ReentrantLock;publicclassTask{privatefinalReentrantLocklocknewReentrantLock();publicvoidrunIfIdle(){// 试着拿锁,拿不到立刻返回 false,不阻塞if(!lock.tryLock()){System.out.println(上一轮还在跑,本轮跳过);return;}try{doWork();}finally{lock.unlock();// 必须放 finally,否则异常了锁泄漏}}privatevoiddoWork(){/* ... */}}还能带超时:「最多等 2 秒,还拿不到就放弃」:importjava.util.concurrent.TimeUnit;publicbooleanrunWithTimeout()throwsInterruptedException{// 最多等 2 秒if(!lock.tryLock(2,TimeUnit.SECONDS)){returnfalse;// 超时,放弃}try{doWork();returntrue;}finally{lock.unlock();}}这里有个必须记死的纪律:ReentrantLock要手动unlock,而且必须放在finally里。synchronized是 JVM 帮你在退出(含异常)时自动解锁,ReentrantLock没这待遇,忘了unlock或者中途抛异常没进 finally,锁就永远不释放,后面所有线程全卡死。这是从synchronized转过来最容易翻的车。局限二:线程卡在锁上想中断它 → lockInterruptibly被synchronized阻塞的线程无法响应中断——你thread.interrupt()它,它照样在那儿等着。这在需要「优雅停机」「取消长时间等待」的场景很致命。ReentrantLock.lockInterruptibly()让等锁的线程可以被中断:publicvoiddoInterruptibly()throwsInterruptedException{// 等锁期间若被 interrupt(),抛 InterruptedException 跳出lock.lockInterruptibly();try{doWork();}finally{lock.unlock();}}配合线程池优雅关闭:shutdownNow()会给工作线程发中断,用lockInterruptibly()的线程就能从等锁状态里跳出来收尾退出,而用synchronized的线程会继续傻等,拖住整个关闭流程。局限三:读多写少想并发读 → 换 ReadWriteLock严格说这不是ReentrantLock的功能,而是它的兄弟ReentrantReadWriteLock,但选型时一起考虑。synchronized和ReentrantLock都是「互斥锁」:哪怕两个线程都只是读,也要排队。对于「读远多于写」的缓存类场景,这是巨大的浪费。importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantReadWriteLock;importjava.util.HashMap;importjava.util.Map;publicclassCache{privatefinalMapString,StringmapnewHashMap();privatefinalReentrantReadWriteLockrwLocknewReentrantReadWriteLock();// 字段不能用 var,老实写 Lock 类型privatefinalLockreadLockrwLock.readLock();privatefinalLockwriteLockrwLock.writeLock();publicStringget(Stringkey){readLock.lock();// 多个读线程可同时持有读锁try{returnmap.get(key);}finally{readLock.unlock();}}publicvoidput(Stringkey,Stringvalue){writeLock.lock();// 写锁独占,期间所有读写都挡住try{map.put(key,value);}finally{writeLock.unlock();}}}读锁共享、写锁独占。读多写少时并发度远高于普通互斥锁。但注意:如果写并不罕见,读写锁的开销和写饥饿问题可能反而不如直接用ConcurrentHashMap。选它的前提是明确的「读 写」。局限四:精确唤醒某一类等待线程 → Conditionsynchronized配wait()/notify()/notifyAll()做线程协作时,有个硬伤:只有一个「等待队列」。生产者-消费者模型里,队列满时生产者等待、队列空时消费者等待,它们共用同一个等待集合,你只能notifyAll()全叫醒,然后大家抢着重新判断条件,效率低还容易惊群。ReentrantLock能创建多个Condition,每类等待线程各用一个,精确唤醒:importjava.util.concurrent.locks.Condition;importjava.util.concurrent.locks.ReentrantLock;publicclassBoundedBufferT{privatefinalObject[]items;privateintcount,putIdx,takeIdx;privatefinalReentrantLocklocknewReentrantLock();privatefinalConditionnotFulllock.newCondition();// 生产者等这个privatefinalConditionnotEmptylock.newCondition();// 消费者等这个publicBoundedBuffer(intcap){itemsnewObject[cap];}publicvoidput(Tx)throwsInterruptedException{lock.lock();try{while(countitems.length){notFull.await();// 满了,生产者在 notFull 上等}items[putIdx]x;putIdx(putIdx1)%items.length;count;notEmpty.signal();// 只唤醒一个消费者,不惊动其他生产者}finally{lock.unlock();}}SuppressWarnings(unchecked)publicTtake()throwsInterruptedException{lock.lock();try{while(count0){notEmpty.await();// 空了,消费者在 notEmpty 上等}Tx(T)items[takeIdx];takeIdx(takeIdx1)%items.length;count--;notFull.signal();// 只唤醒一个生产者returnx;}finally{lock.unlock();}}}put时只notEmpty.signal()唤醒消费者,take时只notFull.signal()唤醒生产者,各叫各的,没有惊群。这是synchronized单一等待队列做不到的。注意await()依然要放在while循环里判断条件(防虚假唤醒),这点和wait()一样。顺带一提:公平锁ReentrantLock构造时传true可以要公平锁,synchronized只能是非公平的:// 公平锁:先到先得,严格按等待顺序拿锁privatefinalReentrantLockfairLocknewReentrantLock(true);// 默认非公平:允许插队,吞吐更高privatefinalReentrantLocklocknewReentrantLock();公平锁避免了线程饥饿(某个倒霉线程一直抢不到),但代价是吞吐量明显下降——因为要严格维护队列顺序、频繁上下文切换。默认(非公平)几乎总是更好的选择,只有在明确观察到「某些线程长期饿死」且业务不能接受时,才考虑公平锁。别一上来就new ReentrantLock(true),那通常是负优化。选型结论把上面的分析压缩成一张决策表:你的需求该用什么简单互斥,拿不到就等synchronized(默认首选)抢不到锁要放弃/超时ReentrantLock.tryLock()等锁期间要能被中断ReentrantLock.lockInterruptibly()读远多于写ReentrantReadWriteLock要精确唤醒某类等待线程ReentrantLock 多个Condition要严格 FIFO 防饥饿ReentrantLock(true)公平锁一句话:没有上面那些特殊需求,就用synchronized;需要「可中断、可超时、可尝试、多条件、公平」中的任何一个,才换ReentrantLock。别因为「听说 Lock 更高级」就无脑替换——现代 JVM 对synchronized优化得很好,而ReentrantLock那个「必须手动 finally unlock」的负担,是实打实会引入锁泄漏 bug 的。小结synchronized是默认首选:JVM 内建、自动释放、JIT 优化好,简单互斥场景没有理由不用它。ReentrantLock的价值在于四个synchronized干不了的能力:tryLock(可尝试/超时)、lockInterruptibly(可中断)、多Condition(精确唤醒)、公平锁选项。用ReentrantLock必须把unlock()放进finally,否则异常路径会锁泄漏——这是它相比synchronized唯一但致命的心智负担。读多写少考虑ReentrantReadWriteLock,但写不罕见时可能不如直接上ConcurrentHashMap;公平锁默认别开,它是负优化的常见来源。记忆点:能力越强,责任越大——ReentrantLock给你更多控制权,也把「记得解锁」的责任交还给了你。按需求选,不按「谁更高级」选。
延伸阅读

更多相关文章

2026/11/2 13:51:57

Redis单线程高性能原理深度解析:从I/O多路复用到架构权衡

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:49:44

重卡充电站怎么选址?能效电气用S1200和S2500来打样

2026年,新能源重卡市场迎来了真正的爆发时刻。根据交强险数据,2025年12月,新能源重卡渗透率已提升至53.89%,全年销量达到23.32万辆,同比增长181.91%。预计2026年,新能源重卡平均渗透率有望突破35%&#xff…

2026/11/2 16:00:12

从双击到内核:一次文件打开背后的操作系统原理

从双击到内核:一次文件打开背后的操作系统原理文件系统的基本全貌(宏观视角)1.1 文件与文件系统文件也有一些分类。按逻辑结构分类如下所示。图1 文件逻辑结构分类标题文件系统,首先包括的当然是当前存储容器中的所有文件。如果仅…

2026/11/5 13:55:19

GD32E10x官方例程入门:从GPIO点灯到串口调试

简介:这份GD32E10x系列官方例程面向嵌入式硬件与单片机开发者,基于ARM Cortex-M3内核,汇集兆易创新GD32E10x的示例代码,适合工业控制、消费电子、通信设备等场景的学习、验证与二次开发。压缩包共935个文件,约11.16MB&…

2026/11/5 13:50:18

基于Django的运维平台实战:资产、发布、巡检、告警一体化

简介:这份资源是一套基于Django框架开发的运维平台源码,面向具备Python基础、希望了解Web运维系统实现思路的开发者与运维人员,可用于学习主机管理、服务监控、日志查看、任务调度、权限控制、资源统计、脚本执行与故障告警等常见运维模块的落…

2026/11/5 13:25:11

基于ADS122U04IPWR与STM32F427ZI的工业测温方案设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 13:20:10

Dubbo服务下线实时告警:基于ZooKeeper Watch的轻量级监控方案

简介:这是一套面向Java分布式系统运维与开发人员的Dubbo服务高可用监控解决方案,聚焦于Provider异常下线的实时感知与告警响应。资源通过Zookeeper监听机制实现服务状态变更捕获,结合邮件通知模块,在不侵入原有Dubbo代码的前提下&…

2026/11/5 13:20:10

SSM超市管理系统源码解析:从分层架构到本地部署与业务修改

简介:这份资源是面向高校计算机专业学生与Java初学者的一套SSM框架课程设计完整项目,围绕超市管理场景,帮助读者完成从框架整合到业务模块落地的实战练习,适合作为毕业设计或课程作业的参考方案。压缩包共792个文件,约…

2026/11/5 13:20:10

SSM超市管理系统实战:从课设跑通到二次开发全解析

简介:这份资源是面向高校计算机相关专业学生与Java入门开发者的SSM课程设计参考项目,围绕超市管理场景,解决商品、库存、销售等业务模块从设计到落地的实践需求,适合作为课程作业、毕业设计原型或SSM框架练手案例。压缩包共792个文…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑