ReentrantLock锁江湖:一柄寒刃镇并发纷争
目录
- 江湖乱世,锁镇八方
- 第一式:锁起江湖——显式锁的武林地位
- 【锁道沧桑】synchronized老掌门的三道硬伤
- 硬伤一:招式笨重,死等到底
- 硬伤二:闭关死守,一屋乱炖
- 硬伤三:天赋随机,不讲武德
- 【新秀崛起】ReentrantLock少侠的拜山帖
- 招式·显式锁契约
- 破局·专治老掌门三宗罪
- 【江湖警训】锁魂附体的代价
- 第二式:锁魂心法——七大核心奥义
- 【开篇】寒刃藏锋,七诀断生死
- 奥义一:可重入真气(Reentrant)
- 奥义二:公平/非公平抉择(Fairness)
- 奥义三:锁中断秘术(lockInterruptibly)
- 奥义四:锁时限绝技(tryLock)
- 奥义五:多条件变量(Condition)
- 奥义六:锁状态窥视
- 奥义七:锁的界限突破
- 【心法总纲】
- 第三式:锁战六诀——刀尖舔血的生死局
- 【开篇】刀光剑影见真章
- 第一诀:生死契约(try-finally范式)
- 第二诀:锁中锁(可重入实战)
- 第三诀:银库风云(Condition精准唤醒)
- 第四诀:锁的生死劫(tryLock破僵局)
- 第五诀:锁魂反噬(死锁现场)
- 第六诀:锁的分身术(锁粒度控制)
- 【锁战心法】
- 第四式:锁间论剑——与诸神对比
- 【开篇】华山之巅,群锁争锋
- 第一场:新旧霸主之战(synchronized VS ReentrantLock)
- synchronized老宗师
- ReentrantLock少侠
- 第二场:分治之道(ReadWriteLock 读写锁)
- 黑白双煞的合击技
- 第三场:锁外有天(其他并发神器)
- 锁的宿敌——原子变量(Atomic)
- 锁的克星——线程本地存储(ThreadLocal)
- 【论剑终章】
- 第五式:锁道天机——深藏功与名
- 【开篇】锁魂寒刃,内藏乾坤
- 天机一:AQS锁魂阵——并发江湖的底层规则
- 锁魂阵结构(CLH队列)
- 锁魂符操控术(state变量)
- 天机二:锁魂寒刃铸造术(ReentrantLock与AQS的关系)
- 寒刃剖解图
- 闯阵流程(以非公平锁为例)
- 天机三:锁道性能终极奥义
- 心法一:锁粒度化整为零
- 心法二:锁耗时与锁竞争平衡术
- 心法三:锁与线程池的默契
- 【终章】天机不可泄,修行在个人
江湖乱世,锁镇八方
“武林至尊,宝刀synchronized;号令天下,莫敢不从;ReentrantLock不出,谁与争锋?”
江湖多线程乱战,资源如黄金镖局,群雄并起抢夺。synchronized老盟主虽稳,怎奈招式古板,今有少侠ReentrantLock持玄铁寒刃出山,且看这新秀如何搅动风云!
第一式:锁起江湖——显式锁的武林地位
【锁道沧桑】synchronized老掌门的三道硬伤
江湖传言,synchronized
老掌门坐镇并发武林三十载,一袭synchronized
长袍护体,举手投足间锁住临界区。然岁月不饶人,老掌门有三道致命硬伤,终被少侠们诟病:
硬伤一:招式笨重,死等到底
“锁来!”
老掌门一旦出手锁人,必是不死不休的架势。线程若抢不到锁,便如被点了穴道般原地僵直,既不能喊一声"老子不等了!"(不可中断),也不能掐表看香(无超时机制)。遇上死锁僵局,轻则线程饿死,重则系统咳血崩殂。
硬伤二:闭关死守,一屋乱炖
老掌门家中只有一间茅庐(单条件队列),管你是等银子的还是等镖车的,统统挤在屋檐下打地铺。唤醒线程时只能大喝一声"都起来!",结果一群人等错了信号,白忙活一场。效率?不存在的!
硬伤三:天赋随机,不讲武德
老掌门调度线程全凭心情——非公平锁乃是祖传规矩。后来的线程可能插队先得锁,先来的好汉反被晾在一边。吞吐量虽高,却寒了江湖义士的心。
【新秀崛起】ReentrantLock少侠的拜山帖
正当江湖苦synchronized
久矣,一袭黑袍的ReentrantLock
少侠横空出世,怀中揣着java.util.concurrent.locks
战书,剑指并发江湖!
招式·显式锁契约
Lock lock = new ReentrantLock(); // 玄铁寒刃淬火而生
lock.lock(); // 剑鸣出鞘,锁定乾坤
try {
// 此处乃护镖重地
// 刀光剑影间操作共享资源
} finally {
lock.unlock(); // 收剑归鞘!纵使代码走火入魔,此招必出!
}
少侠剑法显式至极——何时拔剑(lock())、何时收剑(unlock()),全凭开发者号令。对比synchronized
老掌门的隐式闭穴手法,少侠的剑招多了三分掌控,七分灵活。
破局·专治老掌门三宗罪
-
中断响应:
少侠轻功了得,lockInterruptibly()
剑招可随时被一记interrupt()
飞镖打断:“告辞!这锁老子不要了!” -
多间厢房:
挥手间造出多个Condition对象,等银子的去东厢房,等镖车的住西厢房。唤醒时signal()
精准点杀,再无鸡飞狗跳之乱。 -
公平可选:
少侠有两副面孔——new ReentrantLock(true)
:公平锁,先来后到讲道义new ReentrantLock(false)
:非公平锁(默认),后来居上拼手速
【江湖警训】锁魂附体的代价
然显式锁如七伤拳,功力愈深,责任愈重!
- 若忘写
unlock()
,便是锁魂附体,线程永堕无间地狱(死锁) - 若
lock()
与unlock()
招式不成对,轻则数据错乱,重则系统暴毙
“显式锁之道,如持利刃切豆腐——用好了是庖丁解牛,用岔了便是自断经脉!”
少侠们切记:try-finally
护体咒,乃是锁江湖的第一保命符!
第二式:锁魂心法——七大核心奥义
【开篇】寒刃藏锋,七诀断生死
“重入锁非铁,心法通幽冥”
ReentrantLock少侠的剑法看似简单,实则暗藏七重内功心法。今日便揭开锁魂寒刃上的符文,且看这七道奥义如何斩线程于无形!
奥义一:可重入真气(Reentrant)
口诀:“一锁七进七出,自家后院随意逛!”
- 同一线程可重复获取同一把锁,锁计数器+1
- 若某线程已持锁,再闯关时无需排队(锁计数器叠加)
lock.lock(); // 第一重锁
lock.lock(); // 第二重锁(计数器=2)
try {
偷看藏经阁();
潜入炼丹房();
} finally {
lock.unlock(); // 解锁第二重(计数器=1)
lock.unlock(); // 解锁第一重(计数器=0)
}
若只解一重锁便跑路?嘿嘿,锁魂咒附体,其他线程永世不得翻身!
奥义二:公平/非公平抉择(Fairness)
剑分两刃:
-
公平锁(Fair)
- “先到者先得,江湖自有公道在!”
- 底层CLH队列严格按先来后到排队
- 代价:线程切换频繁,吞吐量稍逊
ReentrantLock fairLock = new ReentrantLock(true); // 公平之剑
-
非公平锁(Unfair,默认)
- “手快有,手慢无!”
- 新线程可直接插队抢锁(减少线程切换)
- 吞吐量暴涨,但可能饿死老线程
ReentrantLock unfairLock = new ReentrantLock(); // 默认饮血剑
奥义三:锁中断秘术(lockInterruptibly)
破局绝招:“任你锁住千山,我自抽身而去!”
- 传统
synchronized
若抢不到锁,只能死等(如同被点了死穴) - ReentrantLock可响应中断信号,优雅撤退:
try {
lock.lockInterruptibly(); // 摆出夺锁架势,但耳听八方
// 抢锁成功...
} catch (InterruptedException e) {
// 突闻撤退号角!收功疗伤,放弃夺锁
Thread.currentThread().interrupt();
}
适用场景:防止线程因死锁永久挂起,给线程留条活路
奥义四:锁时限绝技(tryLock)
江湖黑话:“一炷香拿不下山头,爷去隔壁发财!”
- 尝试抢锁,若不成则立即返回或等待指定时间
- 避免无底洞式等待,代码更健壮
// 闪电战:抢到就抢,抢不到拉倒
if (lock.tryLock()) {
try { /* 抢山头成功 */ }
finally { lock.unlock(); }
} else {
去抢其他山头();
}
// 持久战:最多等3秒
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try { /* 三秒内抢到 */ }
finally { lock.unlock(); }
} else {
呼叫援军();
}
奥义五:多条件变量(Condition)
分而治之:“东厢等银子,西厢等镖车,精准唤醒不添乱!”
- synchronized只能用
wait()/notifyAll()
一锅炖 - ReentrantLock可创建多个Condition对象,实现精准唤醒
Condition 银子就位 = lock.newCondition();
Condition 镖车空闲 = lock.newCondition();
// 等银子的线程
银子就位.await();
// 银子到账后
银子就位.signal(); // 只唤醒等银子的,不惊动等镖车的
对比
synchronized
的notifyAll()
:一嗓子吼醒所有人,结果80%线程白跑一趟
奥义六:锁状态窥视
天眼通:“锁有几重?谁人持有?尽在掌握!”
getHoldCount()
:查当前线程重入次数isHeldByCurrentThread()
:是否被当前线程持有hasQueuedThreads()
:是否有线程在排队
if (lock.isHeldByCurrentThread()) {
System.out.println("当前锁重数:" + lock.getHoldCount());
}
诊断死锁的利器,但实战慎用——窥探锁状态可能引发线程安全问题
奥义七:锁的界限突破
以柔克刚:“锁外乾坤大,锁内日月长!”
- 锁的细粒度控制:
- 长任务分段加锁,减少锁持有时间
- 读写分离(需配合ReadWriteLock)
- 锁的粗粒度慎用:
- 避免在锁内调用外星方法(外部接口),防止死锁蔓延
【心法总纲】
"七诀融会贯通日,便是锁道大成时!
然需谨记:
- unlock()必放finally,否则锁魂咒反噬
- Condition唤醒用signal()非signalAll(),避免无谓争斗
- 公平锁代价高,非必要不开启
诸君,下一式《锁战十三诀》将见血封喉,且看如何用这七诀破尽天下并发难题!"
第三式:锁战六诀——刀尖舔血的生死局
【开篇】刀光剑影见真章
“纸上得来终觉浅,绝知锁事要码战!”
江湖传言,ReentrantLock少侠的七重心法已臻化境。然锁道凶险,稍有不慎便是满盘皆输。今以十三道生死局,看少侠如何斩破并发乱象!
第一诀:生死契约(try-finally范式)
江湖规矩:“拔剑必归鞘,解锁写finally!”
ReentrantLock lock = new ReentrantLock();
void 押镖() {
lock.lock(); // 寒刃出鞘
try {
清点镖银();
雇佣镖师();
路途凶险(); // 可能抛出异常!
} finally {
lock.unlock(); // 收剑!纵使刀山火海也必执行
}
}
血泪教训:某门派弟子忘写unlock,十万线程困死锁魂阵,系统七七四十九日未响应!
第二诀:锁中锁(可重入实战)
连环局:“我锁我自己,七进七出如入无人之境!”
public class 藏宝阁 {
private final ReentrantLock lock = new ReentrantLock();
public void 取宝物() {
lock.lock();
try {
验明身份();
取外层宝物();
取内层秘宝(); // 嵌套调用需重入
} finally {
lock.unlock();
}
}
private void 取内层秘宝() {
lock.lock(); // 二次加锁
try {
破解机关();
} finally {
lock.unlock();
}
}
}
少侠须知:重入锁计数器必须归零,多lock()一次就要多unlock()一次!
第三诀:银库风云(Condition精准唤醒)
分金定穴:“东厢等银子,西厢等镖车,莫要乱成一锅粥!”
public class 镖局银库 {
private final ReentrantLock lock = new ReentrantLock();
private final Condition 银子充足 = lock.newCondition();
private final Condition 镖车空闲 = lock.newCondition();
private int 银子数量 = 0;
private int 镖车数量 = 3;
// 存银子的线程
public void 存银子(int 两) throws InterruptedException {
lock.lock();
try {
银子数量 += 两;
银子充足.signalAll(); // 只唤醒等银子的
} finally {
lock.unlock();
}
}
// 取银子的线程
public void 取银子(int 两) throws InterruptedException {
lock.lock();
try {
while (银子数量 < 两) {
银子充足.await(); // 进东厢房等待
}
银子数量 -= 两;
} finally {
lock.unlock();
}
}
// 申请镖车的线程
public void 申请镖车() throws InterruptedException {
lock.lock();
try {
while (镖车数量 == 0) {
镖车空闲.await(); // 进西厢房等待
}
镖车数量--;
} finally {
lock.unlock();
}
}
}
精髓:
signal()
与signalAll()
如同传音入密,定向唤醒避免无谓争抢!
第四诀:锁的生死劫(tryLock破僵局)
闪电战法:“三秒拿不下山头,转攻他处!”
public class 攻城略地 {
private final ReentrantLock 城门锁 = new ReentrantLock();
public void 破城门() {
// 最多等5秒
try {
if (城门锁.tryLock(5, TimeUnit.SECONDS)) {
try {
架云梯();
投石车进攻();
} finally {
城门锁.unlock();
}
} else {
绕道偷袭(); // 超时策略
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
鸣金收兵();
}
}
}
战报:某将军死等城门锁,苦候三日粮草耗尽,被tryLock少侠半柱香连破十城!
第五诀:锁魂反噬(死锁现场)
血案实录:“两锁相争,少侠殒命!”
// 错误示范!禁止模仿!
public class 死锁惨案 {
private final ReentrantLock 锁A = new ReentrantLock();
private final ReentrantLock 锁B = new ReentrantLock();
public void 线程1() {
锁A.lock();
try {
Thread.sleep(100); // 故意制造死锁时机
锁B.lock(); // 等待被线程2持有的锁B
try { /* ... */ }
finally { 锁B.unlock(); }
} finally { 锁A.unlock(); }
}
public void 线程2() {
锁B.lock();
try {
Thread.sleep(100);
锁A.lock(); // 等待被线程1持有的锁A
try { /* ... */ }
finally { 锁A.unlock(); }
} finally { 锁B.unlock(); }
}
}
验尸报告:两线程各持一锁,互相等待,CPU使用率100%却无进展!
解法:用tryLock
+超时,或按固定顺序获取锁!
第六诀:锁的分身术(锁粒度控制)
庖丁解牛:“大锁化小锁,吞吐量飙升!”
public class 钱庄账本 {
// 粗粒度锁:整个账本一把锁(性能低下)
// private final ReentrantLock 大锁 = new ReentrantLock();
// 细粒度锁:分账户上锁
private final Map<String, ReentrantLock> 账户锁池 = new ConcurrentHashMap<>();
public void 转账(String 来自, String 转到, int 金额) {
// 获取两把锁(需防死锁)
ReentrantLock 锁1 = 账户锁池.computeIfAbsent(来自, k -> new ReentrantLock());
ReentrantLock 锁2 = 账户锁池.computeIfAbsent(转到, k -> new ReentrantLock());
// 按固定顺序加锁(避免死锁)
if (来自.compareTo(转到) < 0) {
锁1.lock();
锁2.lock();
} else {
锁2.lock();
锁1.lock();
}
try {
扣减账户(来自, 金额);
增加账户(转到, 金额);
} finally {
锁1.unlock();
锁2.unlock();
}
}
}
精髓:锁的粒度越细,并发度越高,但复杂度也越高!
【锁战心法】
"十三诀虽利,仍需牢记:
- 锁范围最小化:锁内代码如闪电,快进快出
- 死锁四条件:互斥、持有等待、不可剥夺、循环等待,破其一可解
- 监控工具:jstack、VisualVM等如同天眼,死锁无所遁形
明日终章,《锁道天机》将揭晓AQS底层奥义,少侠们且去消化这十三道生死局!"
第四式:锁间论剑——与诸神对比
【开篇】华山之巅,群锁争锋
“重入锁虽利,终非天下无敌!”
江湖并发武林,ReentrantLock少侠持寒刃登顶华山。但见东峰synchronized老宗师闭目养神,西岭ReadWriteLock黑白双煞虎视眈眈,且看这锁界诸神如何论剑!
第一场:新旧霸主之战(synchronized VS ReentrantLock)
synchronized老宗师
兵器谱特性:
- 隐式锁:无需手动解锁,JVM自动护法
- 代码简洁:一招
synchronized
走天下 - 性能飞跃:JDK6后优化锁升级(偏向锁->轻量级锁->重量级锁)
杀手锏:
public synchronized void 武林秘籍() {
// 自动加锁解锁,新手友好
}
Object lock = new Object();
public void 江湖规矩() {
synchronized(lock) { // 锁对象随心选
// 临界区
}
}
破绽:
- 招式单调(无tryLock、无法中断等待)
- 单条件队列(一嗓子唤醒所有线程)
- 非公平锁(默认见缝插针)
ReentrantLock少侠
兵器谱特性:
- 显式锁:精准控制加锁解锁
- 七十二变:可中断、超时、公平锁、多条件
- 吞吐量之王:高并发场景性能更优
生死簿对比:
特性 | synchronized | ReentrantLock |
---|---|---|
锁获取方式 | 隐式(JVM管理) | 显式(手动lock/unlock) |
锁中断 | ❌ 死等到底 | ✅ lockInterruptibly() |
锁超时 | ❌ 无 | ✅ tryLock(时间) |
公平锁 | ❌ 只有非公平 | ✅ 可配置公平/非公平 |
条件变量 | 1个Object监视器 | 多个Condition对象 |
性能 | JDK6后大幅优化 | 高竞争下更优 |
战局点评:
- 新手村任务:简单同步选
synchronized
,代码干净不易出错 - 修罗战场:高并发、复杂逻辑必用
ReentrantLock
,吞吐量碾压 - 隐藏奥义:JDK17后
synchronized
与ReentrantLock
性能差距缩小,但灵活度仍是硬伤
第二场:分治之道(ReadWriteLock 读写锁)
黑白双煞的合击技
“写锁独占九重天,读锁共享江湖义!”
ReadWriteLock双生子——写锁(独占)与读锁(共享),专治读写混合的并发乱局。
心法演示:
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock 读锁 = rwLock.readLock();
Lock 写锁 = rwLock.writeLock();
// 读线程(可并发)
public void 查账本() {
读锁.lock();
try {
// 众人皆可同时查阅
} finally {
读锁.unlock();
}
}
// 写线程(独占)
public void 改账本() {
写锁.lock();
try {
// 闲人退散,独享修改
} finally {
写锁.unlock();
}
}
战局分析:
- 读多写少:读写锁吞吐量吊打
synchronized
和ReentrantLock
- 写多读少:反受其害,不如直接用独占锁
- 锁升级陷阱:读锁不能直接升级写锁(否则死锁)!
第三场:锁外有天(其他并发神器)
锁的宿敌——原子变量(Atomic)
“以无锁之道,破万锁之局!”
CAS(Compare And Swap)算法实现的原子变量,轻量如飞刀,专克低竞争场景。
AtomicInteger 江湖排名 = new AtomicInteger(0);
void 挑战武林盟主() {
江湖排名.incrementAndGet(); // CAS自增,无锁并发
}
适用场景:计数器、状态标志等简单操作
锁的克星——线程本地存储(ThreadLocal)
“各扫门前雪,莫管他人锁!”
每个线程独立副本,彻底避免竞争。
ThreadLocal<SimpleDateFormat> 日期格式 = ThreadLocal.withInitial(
() -> new SimpleDateFormat("yyyy-MM-dd")
);
致命缺陷:内存泄漏风险(需及时remove())
【论剑终章】
“锁无高下,唯有合适!”
- synchronized:适合简单同步,新手之友
- ReentrantLock:复杂高并发,老江湖首选
- ReadWriteLock:读多写少,分治之道
- 原子变量:轻量级操作,四两拨千斤
- ThreadLocal:彻底避战,独善其身
少侠切记:
- 并发不是炫技场,最简单的方案才是最好的
- 能不用锁尽量不用(如用并发集合、不可变对象)
- 若必须用锁,锁粒度要细,持有时间要短
第五式:锁道天机——深藏功与名
【开篇】锁魂寒刃,内藏乾坤
“江湖皆道重入锁利,却不知其锋芒之下,暗藏玄机!”
ReentrantLock少侠的剑法看似凌厉,实则依托一套上古秘传心法——AQS(AbstractQueuedSynchronizer)。今日便揭开锁魂寒刃的铸剑图谱,窥探并发武学的终极奥义!
天机一:AQS锁魂阵——并发江湖的底层规则
锁魂阵结构(CLH队列)
“三魂七魄入阵来,锁尽天下不平事!”
AQS以CLH队列为基,构建锁魂大阵:
- Node节点:每个等待线程化为一个魂符(Node),内含线程引用、等待状态
- 双向队列:魂符首尾相连,形成锁魂链(FIFO队列)
- 自旋探测:前驱节点释放锁时,后置节点自旋尝试获取(非完全休眠)
锁魂符操控术(state变量)
“一符控生死,阴阳转乾坤!”
AQS核心通过volatile int state掌控锁状态:
- 独占模式(ReentrantLock所用):
- state=0:锁魂阵无主
- state>0:锁被持有,数值为重入次数
- 共享模式(如Semaphore):
- state表示可用资源数
// AQS简化版锁魂符操控(源码节选)
public abstract class AbstractQueuedSynchronizer {
private volatile int state;
protected final boolean compareAndSetState(int expect, int update) {
// CAS秘术:无招胜有招!
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
// 其他魂符操控术省略...
}
天机二:锁魂寒刃铸造术(ReentrantLock与AQS的关系)
寒刃剖解图
- Sync内部类:继承AQS,实现锁魂阵具体规则
- NonfairSync:非公平锁(默认),新线程可插队
- FairSync:公平锁,严格按队列顺序
闯阵流程(以非公平锁为例)
- 尝试直接夺锁:CAS修改state,成功则占据阵眼
- 夺锁失败入阵:将线程封装为Node加入队列尾部
- 自旋休眠交替:前驱为头节点时尝试CAS夺锁,失败则休眠(LockSupport.park())
- 破阵而出:头节点释放锁,唤醒后续节点
// 非公平锁夺锁逻辑(简化)
final void lock() {
if (compareAndSetState(0, 1)) // 直接CAS抢锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 入队列等待
}
天机三:锁道性能终极奥义
心法一:锁粒度化整为零
“大锁化小锁,吞吐量翻倍!”
// 错误示范:整个银库一把大锁
ReentrantLock 银库锁 = new ReentrantLock();
// 正确姿势:分柜上锁
ConcurrentHashMap<String, ReentrantLock> 分柜锁 = new ConcurrentHashMap<>();
void 存取银两(String 柜号, int 两) {
ReentrantLock 柜锁 = 分柜锁.computeIfAbsent(柜号, k -> new ReentrantLock());
柜锁.lock();
try {
// 操作单个银柜
} finally {
柜锁.unlock();
}
}
心法二:锁耗时与锁竞争平衡术
- 长任务:优先用公平锁,避免线程饿死
- 短任务:非公平锁减少线程切换,吞吐量更高
- 极端场景:无锁算法(CAS)> 细粒度锁 > 粗粒度锁
心法三:锁与线程池的默契
“线程池如水,锁如堤坝,疏堵结合方成江湖!”
- 核心线程数 ≈ CPU核心数(防过度竞争)
- 锁内禁阻塞:绝不在锁内调用IO等阻塞操作
- 死锁检测:用jstack或Arthas定期扫描锁魂阵
【终章】天机不可泄,修行在个人
“AQS乃并发武林《九阴真经》,读懂源码者方成大器!”
少侠须知:
- 锁魂阵虽强,非万能:高并发场景可结合StampedLock、ForkJoinPool等
- 锁是手段,非目的:业务代码能用并发集合就别再造轮子
- 源码即秘籍:ReentrantLock不足500行,AQS千行代码藏尽江湖
赠少侠三句话:
- 读AQS源码,先观**acquire()/release()**两大脉络
- 调试死锁,牢记jstack -l pid > 锁魂阵.txt
- 性能调优,信奉**“快进快出,能逃则逃”**
(本篇天机泄露过多,老夫遭反噬吐血三升,需闭关三日。少侠若悟得真谛,记得下山行侠仗义!)