
## 引言锁是 Java 并发编程的基础设施。从最古老的 synchronized 到 JDK 5 引入的 ReentrantLock再到 JDK 8 的 StampedLockJava 的锁机制经历了从 JVM 内置到 API 化再到乐观读的演进。理解每种锁的原理和适用场景是写出高效并发代码的前提。---## 一、synchronized### 1.1 基本用法java// 1. 修饰实例方法锁当前实例public synchronized void method() {// 同步代码}// 2. 修饰静态方法锁当前类的 Class 对象public static synchronized void staticMethod() {// 同步代码}// 3. 修饰代码块锁指定对象public void method() {synchronized (lock) {// 同步代码}}### 1.2 锁的原理Monitor每个 Java 对象关联一个 Monitor监视器synchronized 进入monitorenter → 获取 Monitor 锁synchronized 退出monitorexit → 释放 Monitor 锁Monitor 结构┌────────────────────────────┐│ _owner → 持有锁的线程 ││ _entry_set → 等待获取锁 ││ _wait_set → 调用 wait() │└────────────────────────────┘### 1.3 锁升级偏向锁 → 轻量级锁 → 重量级锁JDK 6 引入锁升级优化synchronized 不再总是重量级锁无锁 → 偏向锁 → 轻量级锁 → 重量级锁│ │ │ ││ │ │ └─ 竞争激烈OS 互斥量│ │ └─ 自旋等待CAS适合短时间竞争│ └─ 同一线程重复获取无竞争CAS 都不需要└─ 对象刚创建无任何线程访问升级是单向的不可降级G1 下有例外| 锁状态 | 适用场景 | 获取方式 | 性能 ||--------|----------|----------|------|| 偏向锁 | 同一线程反复获取 | 检查偏向线程 ID | 最优 || 轻量级锁 | 交替执行低竞争 | CAS 自旋 | 优 || 重量级锁 | 高竞争 | OS 互斥量 | 差 |### 1.4 优缺点| 优点 | 缺点 ||------|------|| JVM 内置使用简单 | 无法中断等待锁的线程 || 锁升级自动优化 | 无法设置超时 || 异常自动释放锁 | 不支持公平锁 || 可重入 | 不支持条件变量Condition || | 在虚拟线程中会导致固定Pinning |---## 二、ReentrantLock### 2.1 基本用法javaprivate final ReentrantLock lock new ReentrantLock();public void method() {lock.lock();try {// 同步代码} finally {lock.unlock(); // 必须在 finally 中释放}}### 2.2 公平锁 vs 非公平锁java// 非公平锁默认新来的线程可能插队ReentrantLock unfairLock new ReentrantLock();// 公平锁按等待顺序获取锁ReentrantLock fairLock new ReentrantLock(true);| 对比 | 非公平锁 | 公平锁 ||------|----------|--------|| 获取顺序 | 可能插队 | FIFO 排队 || 吞吐量 | 高 | 低 || 饥饿风险 | 有线程可能永远等不到 | 无 || 实现原理 | CAS 直接尝试 | 先入队列排队 |### 2.3 可中断锁javaReentrantLock lock new ReentrantLock();Thread t new Thread(() - {try {lock.lockInterruptibly(); // 可被中断的锁获取try {doWork();} finally {lock.unlock();}} catch (InterruptedException e) {System.out.println(被中断放弃获取锁);}});t.start();Thread.sleep(1000);t.interrupt(); // 中断等待锁的线程### 2.4 超时获取锁javaReentrantLock lock new ReentrantLock();if (lock.tryLock()) {try {doWork();} finally {lock.unlock();}} else {System.out.println(获取锁失败执行降级逻辑);}// 带超时if (lock.tryLock(5, TimeUnit.SECONDS)) {try {doWork();} finally {lock.unlock();}} else {System.out.println(5秒内未获取锁);}### 2.5 Condition 条件变量java// synchronized 只有一个 wait set// ReentrantLock 支持多个 Condition实现精准唤醒private final ReentrantLock lock new ReentrantLock();private final Condition notFull lock.newCondition();private final Condition notEmpty lock.newCondition();private final Object[] items new Object[10];private int count, putIdx, takeIdx;public void put(Object item) throws InterruptedException {lock.lock();try {while (count items.length) {notFull.await(); // 队列满等待 notFull 条件}items[putIdx] item;if (putIdx items.length) putIdx 0;count;notEmpty.signal(); // 通知消费者} finally {lock.unlock();}}public Object take() throws InterruptedException {lock.lock();try {while (count 0) {notEmpty.await(); // 队列空等待 notEmpty 条件}Object item items[takeIdx];items[takeIdx] null;if (takeIdx items.length) takeIdx 0;count--;notFull.signal(); // 通知生产者return item;} finally {lock.unlock();}}### 2.6 synchronized vs ReentrantLock| 对比 | synchronized | ReentrantLock ||------|-------------|---------------|| 实现 | JVM 内置 | JDK APIAQS || 锁获取 | 自动 | 手动 lock/unlock || 可中断 | ❌ | ✅ lockInterruptibly || 超时 | ❌ | ✅ tryLock(timeout) || 公平性 | 非公平 | 可选公平/非公平 || 条件变量 | wait/notify一个 | Condition多个 || 锁升级 | 偏向→轻量→重量 | 无 || 虚拟线程 | 固定Pinning | 正常卸载 || 异常安全 | 自动释放 | 需 finally 手动释放 |---## 三、ReadWriteLock### 3.1 读写分离java// 读读不互斥读写互斥写写互斥private final ReadWriteLock rwLock new ReentrantReadWriteLock();private final Lock readLock rwLock.readLock();private final Lock writeLock rwLock.writeLock();private MapString, Data cache new HashMap();public Data get(String key) {readLock.lock();try {return cache.get(key);} finally {readLock.unlock();}}public void put(String key, Data value) {writeLock.lock();try {cache.put(key, value);} finally {writeLock.unlock();}}### 3.2 ReentrantReadWriteLock 特性| 特性 | 说明 ||------|------|| 公平性 | 支持公平/非公平模式 || 重入 | 读锁和写锁都支持重入 || 锁降级 | 写锁可降级为读锁获取写锁→获取读锁→释放写锁 || 锁升级 | 读锁不能升级为写锁会死锁 |java// 锁降级写锁 → 读锁writeLock.lock();try {updateData();readLock.lock(); // 获取读锁} finally {writeLock.unlock(); // 释放写锁此时持有读锁}try {readData(); // 在读锁保护下读取} finally {readLock.unlock();}### 3.3 读写锁的问题- **写饥饿**读操作频繁时写线程可能长时间获取不到写锁- **不支持乐观读**即使只是读取也需要获取读锁---## 四、StampedLock### 4.1 核心思想StampedLock 在 ReadWriteLock 基础上引入了**乐观读Optimistic Read**ReadWriteLock读锁 → 获取读锁 → 读取 → 释放读锁每次都要 CASStampedLock乐观读 → 获取戳 → 读取 → 验证戳 → 成功则无需 CAS→ 失败则升级为读锁重试### 4.2 三种模式| 模式 | 说明 | 类比 ||------|------|------|| 写锁Writing | 独占锁与读锁互斥 | ReadWriteLock 的写锁 || 读锁Reading | 共享锁与写锁互斥 | ReadWriteLock 的读锁 || 乐观读Optimistic Reading | 无锁读取验证后使用 | 数据库的乐观锁 |### 4.3 基本用法javaprivate final StampedLock sl new StampedLock();private double x, y;// 写操作void move(double deltaX, double deltaY) {long stamp sl.writeLock();try {x deltaX;y deltaY;} finally {sl.unlockWrite(stamp);}}// 乐观读double distanceFromOrigin() {long stamp sl.tryOptimisticRead(); // 1. 获取乐观读戳double currentX x, currentY y; // 2. 读取数据无锁if (!sl.validate(stamp)) { // 3. 验证戳期间是否有写操作stamp sl.readLock(); // 4. 验证失败升级为读锁try {currentX x;currentY y;} finally {sl.unlockRead(stamp);}}return Math.sqrt(currentX * currentX currentY * currentY);}// 悲观读double distanceFromOriginPessimistic() {long stamp sl.readLock();try {return Math.sqrt(x * x y * y);} finally {sl.unlockRead(stamp);}}### 4.4 锁转换java// 读锁 → 写锁long stamp sl.readLock();try {long ws sl.tryConvertToWriteLock(stamp);if (ws ! 0L) {stamp ws;// 持有写锁} else {sl.unlockRead(stamp);stamp sl.writeLock();}} finally {sl.unlock(stamp);}// 乐观读 → 读锁long stamp sl.tryOptimisticRead();if (!sl.validate(stamp)) {stamp sl.readLock();try {// 重新读取} finally {sl.unlockRead(stamp);}}### 4.5 StampedLock 注意事项| 注意 | 说明 ||------|------|| 不可重入 | 同一线程不能重复获取同一锁 || 不支持 Condition | 没有 newCondition() 方法 || 不要用 interrupt | 不要在 readLock/writeLock 中调用 interrupt || unlock 必须传 stamp | 传入错误的 stamp 会抛 IllegalMonitorStateException || 乐观读适合读多写少 | 写频繁时乐观读经常失败反而更慢 |---## 五、锁选型指南### 5.1 决策树需要锁吗├─ 能否用无锁方案Atomic* / Concurrent集合│ ├─ 能 → 优先无锁│ └─ 不能 ↓├─ 读多写少│ ├─ 是 → 读远多于写│ │ ├─ 是 → StampedLock乐观读│ │ └─ 否 → ReentrantReadWriteLock│ └─ 否 ↓├─ 需要高级功能中断/超时/多条件/公平│ ├─ 是 → ReentrantLock│ └─ 否 → synchronized简单场景优先├─ 在虚拟线程中使用│ ├─ 是 → ReentrantLock避免 synchronized 固定│ └─ 否 → synchronized### 5.2 性能对比大致参考低竞争synchronized ≈ ReentrantLock StampedLock中竞争StampedLock ReentrantLock synchronized高竞争StampedLock(乐观读) ReentrantReadWriteLock ReentrantLock synchronized读多写少StampedLock(乐观读) ReentrantReadWriteLock synchronized### 5.3 速查表| 场景 | 推荐锁 | 原因 ||------|--------|------|| 简单互斥 | synchronized | 简单JVM 优化好 || 需要中断/超时 | ReentrantLock | lockInterruptibly / tryLock || 需要公平锁 | ReentrantLock(true) | synchronized 不支持 || 需要多条件唤醒 | ReentrantLock Condition | synchronized 只有一个 wait set || 读多写少 | StampedLock | 乐观读无锁性能最优 || 虚拟线程中 | ReentrantLock | synchronized 导致固定 || 缓存场景 | ReentrantReadWriteLock | 读写分离 || 高并发点数据 | StampedLock | 乐观读 锁转换 |---## 六、锁的性能优化技巧### 6.1 减小锁粒度java// ❌ 锁范围过大synchronized (this) {validate(data); // 无需锁process(data); // 需要锁log(data); // 无需锁}// ✅ 只锁必要部分validate(data);synchronized (this) {process(data);}log(data);### 6.2 减小锁粒度分段锁java// ❌ 一把大锁synchronized (allAccounts) {transfer(from, to, amount);}// ✅ 分段锁类似 ConcurrentHashMapprivate final Object[] locks new Object[16];{for (int i 0; i locks.length; i) locks[i] new Object();}void transfer(Account from, Account to, int amount) {Object lock1 locks[Math.abs(from.hashCode() % 16)];Object lock2 locks[Math.abs(to.hashCode() % 16)];// 按固定顺序加锁避免死锁Object first System.identityHashCode(lock1) System.identityHashCode(lock2) ? lock1 : lock2;Object second first lock1 ? lock2 : lock1;synchronized (first) {synchronized (second) {from.debit(amount);to.credit(amount);}}}### 6.3 避免锁嵌套java// ❌ 锁嵌套容易死锁synchronized (lockA) {synchronized (lockB) {doWork();}}// ✅ 拆分先获取所有锁再操作// 或使用 tryLock 避免无限等待if (lockA.tryLock()) {try {if (lockB.tryLock()) {try {doWork();} finally {lockB.unlock();}}} finally {lockA.unlock();}}---## 总结| 锁 | 一句话 | 适用场景 ||----|--------|----------|| synchronized | 简单可靠JVM 优化 | 简单互斥低竞争 || ReentrantLock | 功能丰富API 灵活 | 需要中断/超时/公平/多条件 || ReentrantReadWriteLock | 读写分离 | 读多写少但写不极少 || StampedLock | 乐观读极致性能 | 读远多于写追求极致吞吐 |