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 对比学习表

特性synchronizedReentrantLock
实现级别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; } } } }

💡 锁使用最佳实践清单

  1. 总是使用try-finally确保锁释放
  2. 保持锁内代码简短,避免长时间持有锁
  3. 按固定顺序获取多个锁,避免死锁
  4. 优先使用读写锁处理读多写少场景
  5. 考虑锁细化,减小锁粒度
  6. 🚫 避免在锁内调用外部方法,可能持有锁时间过长
  7. 🚫 避免锁嵌套,容易产生死锁

🎯 本章总结与主动回忆检查

🔴 核心必会知识点

  1. ReentrantLock三大特性:可中断、超时、公平性
  2. 条件变量:多条件等待和通知机制
  3. 读写锁:读共享、写独占的访问控制
  4. 锁优化原则:锁细化、避免死锁、读写分离

🧠 主动回忆测试

请合上笔记,回答以下问题

  1. 场景题:厨师A需要刀和案板,厨师B也需要刀和案板,如何避免死锁?
  2. 面试题:synchronized和ReentrantLock最主要的区别是什么?
  3. 调优题:发现库存查询很慢,但更新操作很少,应该用什么锁优化?

💡 参考答案

  1. 场景题答案:按固定顺序获取(都先拿刀再拿案板),或使用tryLock+超时机制。
  2. 面试题答案:最主要区别是ReentrantLock提供可中断、超时、公平锁等高级特性。
  3. 调优题答案:使用ReadWriteLock,多个查询可以并行执行。
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP