JUC-锁机制
第二章:锁机制——厨具的智能权限管理系统
2.1 从synchronized到JUC锁——从"万能钥匙"到"智能门禁"
🍳 厨房场景引入
想象你的厨房里有几把关键的厨具(共享资源),传统的synchronized就像只有一把万能钥匙:
▼java复制代码public class TraditionalKitchen { private final Object masterKey = new Object(); public void useExpensiveKnife() { synchronized (masterKey) { // 拿到万能钥匙 System.out.println(Thread.currentThread().getName() + " 使用高级菜刀"); try { Thread.sleep(2000); // 切菜需要时间 } catch (InterruptedException e) { // 无法响应中断! } } // 自动归还钥匙 } public void useRareIngredient() { synchronized (masterKey) { // 还是同一把钥匙 System.out.println(Thread.currentThread().getName() + " 使用珍贵食材"); // 烹饪... } } }
问题分析(问题解决思维链):
▼plain复制代码现象:厨师A切菜时,厨师B连看食材都不行,效率低下 可能原因: ✅ 只有一把万能钥匙,粒度太粗【单一条件】 ✅ 厨师等待时无法被叫走做其他事【不可中断】 ✅ 无法设置等待时间限制【自动管理】 ✅ 新来的厨师可能插队【非公平】 排查命令:jstack查看BLOCKED线程,arthas监控锁竞争 解决方案:使用更智能的ReentrantLock
🔴 核心必会:synchronized的局限性
| 问题 | 厨房类比 | 影响 |
|---|---|---|
| 不可中断 | 厨师等待时不能被叫走 | 响应性差 |
| 非公平锁 | 新厨师可能插队 | 可能饿死老厨师 |
| 单一条件 | 只有一个等待区 | 无法精细管理 |
| 自动管理 | 自动获取释放 | 灵活性差 |
2.2 ReentrantLock核心特性——智能门禁系统
🏗️ 知识联系图
▼plain复制代码锁创建 → 公平性选择 → 锁获取方式 → 条件变量管理 → 异常处理 ↓ 基础lock() → 可中断lockInterruptibly() → 超时tryLock() → 多条件协调
📊 ReentrantLock vs synchronized 对比学习表
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 实现级别 | JVM关键字 | JDK类 |
| 锁获取 | 自动 | 手动lock()/unlock() |
| 锁释放 | 自动 | 必须finally中unlock() |
| 可中断 | ❌ 不支持 | ✅ lockInterruptibly() |
| 超时获取 | ❌ 不支持 | ✅ tryLock(timeout) |
| 公平锁 | ❌ 非公平 | ✅ 可配置公平/非公平 |
| 条件变量 | ❌ 单一 | ✅ 多条件支持 |
| 性能 | 优化后相当 | 高竞争下更好 |
💻 代码实战:智能厨具权限系统
▼java复制代码import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * 智能厨具管理系统 - ReentrantLock完整示例 */ public class SmartKitchenTools { // 🎯 核心必会:创建智能门禁系统 private final ReentrantLock masterChefKnifeLock = new ReentrantLock(true); // 公平锁-按顺序使用 private boolean isKnifeInUse = false; // 🎯 进阶理解:条件变量 - 不同资源的等待区 private final Condition knifeAvailable = masterChefKnifeLock.newCondition(); private final Condition ingredientPrepared = masterChefKnifeLock.newCondition(); private boolean ingredientsReady = false; /** * 基础用法:获取厨具使用权 */ public void useMasterChefKnife(String chefName) { masterChefKnifeLock.lock(); // 申请使用权限 try { System.out.println("🔑 " + chefName + " 拿到主厨刀权限"); // 模拟复杂的烹饪操作 performComplexCooking(chefName); } finally { masterChefKnifeLock.unlock(); // 🚨 必须释放! System.out.println("🔓 " + chefName + " 释放主厨刀权限"); } } /** * 🎯 核心必会:可中断锁 - 厨师可以被叫走 */ public void useKnifeInterruptibly(String chefName) throws InterruptedException { try { masterChefKnifeLock.lockInterruptibly(); // 可被中断的申请 System.out.println("🔑 " + chefName + " 以可中断方式拿到刀"); // 长时间操作,但可以被中断 for (int i = 0; i < 10; i++) { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(chefName + " 被紧急叫走!"); } System.out.println(chefName + " 切菜步骤 " + (i + 1)); Thread.sleep(500); } } finally { if (masterChefKnifeLock.isHeldByCurrentThread()) { masterChefKnifeLock.unlock(); } } } /** * 🎯 核心必会:超时等待 - 厨师不愿无限等待 */ public boolean useKnifeWithTimeout(String chefName, long timeout, TimeUnit unit) { try { // 尝试获取锁,最多等待指定时间 if (masterChefKnifeLock.tryLock(timeout, unit)) { try { System.out.println("⏰ " + chefName + " 在超时内拿到刀,开始工作"); Thread.sleep(2000); return true; // 成功使用 } finally { masterChefKnifeLock.unlock(); } } else { System.out.println("❌ " + chefName + " 等待超时,改用普通刀具"); return false; // 超时未拿到 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.out.println("❌ " + chefName + " 等待时被中断"); return false; } } private void performComplexCooking(String chefName) { try { Thread.sleep(3000); System.out.println("👨🍳 " + chefName + " 完成复杂烹饪"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }
2.3 条件变量——精细化的等待管理系统
🍳 厨房场景:多条件协作
想象一个复杂菜品需要多个条件:
- 食材准备就绪
- 厨具可用
- 调料齐全
▼java复制代码/** * 🎯 进阶理解:多条件协调的复杂烹饪流程 */ public class CoordinatedCookingSystem { private final ReentrantLock kitchenLock = new ReentrantLock(); // 不同的等待条件 private final Condition ingredientsReady = kitchenLock.newCondition(); private final Condition toolsAvailable = kitchenLock.newCondition(); private final Condition saucePrepared = kitchenLock.newCondition(); private boolean hasIngredients = false; private boolean hasTools = false; private boolean hasSauce = false; /** * 主厨:等待所有条件满足 */ public void masterChefProcess(String chefName) throws InterruptedException { kitchenLock.lock(); try { System.out.println("🧑🍳 " + chefName + " 开始检查准备工作..."); // 条件1:等待食材就绪 while (!hasIngredients) { System.out.println("⏳ " + chefName + " 等待食材准备..."); ingredientsReady.await(); // 释放锁,进入食材等待区 } // 条件2:等待厨具可用 while (!hasTools) { System.out.println("⏳ " + chefName + " 等待厨具..."); toolsAvailable.await(); // 进入厨具等待区 } // 条件3:等待酱汁调配 while (!hasSauce) { System.out.println("⏳ " + chefName + " 等待酱汁..."); saucePrepared.await(); // 进入酱汁等待区 } // 所有条件满足,开始烹饪 System.out.println("🚀 " + chefName + " 所有条件满足,开始烹饪!"); Thread.sleep(2000); } finally { kitchenLock.unlock(); } } /** * 助手:准备食材后通知 */ public void prepareIngredients(String assistantName) { kitchenLock.lock(); try { System.out.println("🥬 " + assistantName + " 准备好食材"); hasIngredients = true; ingredientsReady.signalAll(); // 通知所有等待食材的厨师 System.out.println("📢 " + assistantName + " 通知:食材就绪!"); } finally { kitchenLock.unlock(); } } /** * 工具管理员:发放厨具 */ public void provideTools(String managerName) { kitchenLock.lock(); try { System.out.println("🔪 " + managerName + " 提供厨具"); hasTools = true; toolsAvailable.signalAll(); // 通知等待厨具的厨师 } finally { kitchenLock.unlock(); } } }
2.4 公平锁 vs 非公平锁——排队文化 vs 效率优先
📊 公平性对比学习表
| 特性 | 公平锁 (Fair) | 非公平锁 (Non-fair) |
|---|---|---|
| 获取顺序 | 先来先得,严格排队 | 允许插队,竞争获取 |
| 性能 | 上下文切换多,吞吐量低 | 上下文切换少,吞吐量高 |
| 饥饿问题 | 不会饿死 | 可能饿死早到的线程 |
| 适用场景 | 严格要求公平性 | 默认选择,性能优先 |
💻 代码演示:公平性测试
▼java复制代码public class FairnessTest { public static void main(String[] args) throws InterruptedException { System.out.println("🧪 测试公平锁 vs 非公平锁..."); // 测试非公平锁 testLock("非公平锁", new ReentrantLock(false)); Thread.sleep(2000); // 测试公平锁 testLock("公平锁", new ReentrantLock(true)); } private static void testLock(String lockType, ReentrantLock lock) { System.out.println("\n" + "=".repeat(50)); System.out.println("🔍 测试: " + lockType); System.out.println("=".repeat(50)); // 创建5个厨师线程 List<Thread> chefs = new ArrayList<>(); for (int i = 1; i <= 5; i++) { Thread chef = new Thread(new ChefTask(lock, "厨师-" + i), "厨师-" + i); chefs.add(chef); } // 按顺序启动,模拟先来后到 for (Thread chef : chefs) { chef.start(); try { Thread.sleep(10); // 确保启动顺序 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } // 等待所有厨师完成 for (Thread chef : chefs) { try { chef.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } static class ChefTask implements Runnable { private final ReentrantLock lock; private final String chefName; public ChefTask(ReentrantLock lock, String chefName) { this.lock = lock; this.chefName = chefName; } @Override public void run() { lock.lock(); try { System.out.println("✅ " + chefName + " 获得刀具 - 等待队列长度: " + lock.getQueueLength()); Thread.sleep(100); // 模拟使用时间 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlock(); } } } }
🎯 面试金句:
"默认使用非公平锁,因为在实际应用中,线程调度本身就有不确定性,公平锁的严格排序会带来额外的性能开销。只有在严格要求先来后到的业务场景中才使用公平锁。"
2.5 读写锁——食材库存的智能访问控制
🍳 厨房场景:库存管理系统
食材库存的特点是:
- 读多写少:很多厨师查看库存,很少更新
- 读写分离:查看不影响查看,但更新需要独占
▼java复制代码import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 智能食材库存系统 - 读写锁最佳实践 */ public class SmartInventorySystem { private final Map<String, Integer> inventory = new HashMap<>(); // 🎯 核心必会:创建读写锁 private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); /** * 查看库存 - 多个厨师可同时查看(读锁共享) */ public int checkStock(String ingredient, String chefName) { rwLock.readLock().lock(); // 获取读锁 try { System.out.println("📖 " + chefName + " 查看库存: " + ingredient); Thread.sleep(100); // 模拟查询时间 return inventory.getOrDefault(ingredient, 0); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return 0; } finally { rwLock.readLock().unlock(); } } /** * 更新库存 - 只能一个管理员操作(写锁独占) */ public void updateStock(String ingredient, int quantity, String managerName) { rwLock.writeLock().lock(); // 获取写锁 try { System.out.println("✏️ " + managerName + " 开始更新: " + ingredient + " -> " + quantity); Thread.sleep(500); // 模拟更新操作时间 inventory.put(ingredient, quantity); System.out.println("✅ " + managerName + " 完成更新"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { rwLock.writeLock().unlock(); } } /** * 🎯 进阶理解:锁降级 - 先更新再读取的场景 */ public void updateAndCheck(String ingredient, String managerName) { // 1. 先获取写锁进行更新 rwLock.writeLock().lock(); try { // 执行更新操作 int newValue = inventory.getOrDefault(ingredient, 0) + 1; inventory.put(ingredient, newValue); System.out.println("🔄 " + managerName + " 更新: " + ingredient + " = " + newValue); // 🎯 关键步骤:在释放写锁前获取读锁(锁降级) rwLock.readLock().lock(); } finally { rwLock.writeLock().unlock(); // 释放写锁,降级为读锁 } // 2. 仍然持有读锁,可以安全读取 try { System.out.println("📖 " + managerName + " 降级后读取: " + ingredient + " = " + inventory.get(ingredient)); Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { rwLock.readLock().unlock(); // 释放读锁 } } }
📊 读写锁性能优势对比
| 场景 | 传统synchronized | 读写锁 | 性能提升 |
|---|---|---|---|
| 10读0写 | 串行执行 | 并行执行 | 10倍 |
| 5读5写 | 串行执行 | 读并行,写串行 | 5倍 |
| 0读10写 | 串行执行 | 串行执行 | 相当 |
2.6 锁的最佳实践与性能优化
🔧 问题解决思维链:锁竞争优化实战
现象:高并发时性能下降,线程大量时间处于BLOCKED状态
排查流程:
▼java复制代码public class LockOptimizationKitchen { /** * 🎯 核心必会:锁细化 - 不同资源用不同的锁 */ public class FineGrainedLocking { private final Object knifeLock = new Object(); private final Object stoveLock = new Object(); private final Object pantryLock = new Object(); public void cookComplexDish(String chefName) { // 锁细化:不同步骤使用不同锁 synchronized (knifeLock) { System.out.println(chefName + " 切菜..."); // 其他厨师可以用灶台和食材库 } synchronized (pantryLock) { System.out.println(chefName + " 取食材..."); // 其他厨师可以用刀和灶台 } synchronized (stoveLock) { System.out.println(chefName + " 烹饪..."); // 其他厨师可以用刀和食材库 } } } /** * 🎯 核心必会:避免死锁 - 按固定顺序获取锁 其实下面两个方法体是一样的,主要不同点是全局约定,判断是否有逆序的方法体 */ public class DeadlockPrevention { private final Object knifeLock = new Object(); private final Object cuttingBoardLock = new Object(); // 🚨 危险:可能死锁 public void dangerousMethod(String chefName) { synchronized (knifeLock) { System.out.println(chefName + " 拿到刀"); synchronized (cuttingBoardLock) { System.out.println(chefName + " 拿到案板"); // 工作... } } } // ✅ 安全:固定顺序 public void safeMethod(String chefName) { // 总是先申请刀,再申请案板 synchronized (knifeLock) { synchronized (cuttingBoardLock) { System.out.println(chefName + " 安全使用刀和案板"); // 工作... } } } } /** * 🎯 进阶理解:使用tryLock避免死锁 */ public boolean tryLockApproach(ReentrantLock lock1, ReentrantLock lock2, String chefName, long timeout, TimeUnit unit) { long startTime = System.nanoTime(); long timeoutNanos = unit.toNanos(timeout); while (true) { if (lock1.tryLock()) { try { System.out.println(chefName + " 拿到第一把锁"); if (lock2.tryLock()) { try { System.out.println(chefName + " 成功拿到两把锁!"); return true; // 成功! } finally { lock2.unlock(); } } } finally { lock1.unlock(); } } // 检查超时 if (System.nanoTime() - startTime > timeoutNanos) { System.out.println(chefName + " 获取锁超时"); return false; } // 随机等待后重试 try { Thread.sleep(10 + (long)(Math.random() * 50)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } } }
💡 锁使用最佳实践清单
- ✅ 总是使用try-finally确保锁释放
- ✅ 保持锁内代码简短,避免长时间持有锁
- ✅ 按固定顺序获取多个锁,避免死锁
- ✅ 优先使用读写锁处理读多写少场景
- ✅ 考虑锁细化,减小锁粒度
- 🚫 避免在锁内调用外部方法,可能持有锁时间过长
- 🚫 避免锁嵌套,容易产生死锁
🎯 本章总结与主动回忆检查
🔴 核心必会知识点
- ReentrantLock三大特性:可中断、超时、公平性
- 条件变量:多条件等待和通知机制
- 读写锁:读共享、写独占的访问控制
- 锁优化原则:锁细化、避免死锁、读写分离
🧠 主动回忆测试
请合上笔记,回答以下问题:
- 场景题:厨师A需要刀和案板,厨师B也需要刀和案板,如何避免死锁?
- 面试题:synchronized和ReentrantLock最主要的区别是什么?
- 调优题:发现库存查询很慢,但更新操作很少,应该用什么锁优化?
💡 参考答案
- 场景题答案:按固定顺序获取(都先拿刀再拿案板),或使用tryLock+超时机制。
- 面试题答案:最主要区别是ReentrantLock提供可中断、超时、公平锁等高级特性。
- 调优题答案:使用ReadWriteLock,多个查询可以并行执行。
评论
问答助学
相关内容
0个评论
全部评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
