【锁机制详解】

本文章已经生成可运行项目,

💡 摘要:你是否知道 synchronized 锁对象时,锁的到底是什么?
是否在 ReentrantLocktryLock()lockInterruptibly() 之间困惑?
Java 中的“锁”远不止 synchronized
JVM 内置锁JUC 显式锁
悲观锁乐观锁
可重入公平性锁升级
本文将带你深入剖析 Java 锁的 7 大核心机制
揭秘 synchronized偏向锁 → 轻量级锁 → 重量级锁 升级过程,
解析 ReentrantLock 基于 AQS (AbstractQueuedSynchronizer) 的实现原理,
对比 synchronizedReentrantLock 的性能与适用场景,
并介绍 StampedLockReadWriteLock 等高级锁。
文末附锁选型决策树面试高频问题
助你彻底掌握 Java 并发的“定海神针”。


一、锁的本质:保护共享资源

在多线程环境下,多个线程并发访问共享的可变资源时,必须通过锁机制来保证原子性、可见性、有序性,防止数据不一致。

public class Counter {
    private int count = 0;

    // ❌ 线程不安全!
    public void unsafeIncrement() {
        count++; // 读 → 加 → 写,非原子
    }

    // ✅ 线程安全
    public synchronized void safeIncrement() {
        count++; // synchronized 保证了原子性
    }
}

🔐 锁的作用:确保同一时刻只有一个线程能进入临界区。


二、Java 锁的分类

分类维度类型说明
实现方式内置锁 (Intrinsic Lock)synchronized 关键字
显式锁 (Explicit Lock)java.util.concurrent.locks.Lock 接口实现
获取方式悲观锁 (Pessimistic Lock)假设冲突,先加锁再操作 (synchronizedReentrantLock)
乐观锁 (Optimistic Lock)假设无冲突,操作后检查 (CASAtomic 类)
可重入性可重入锁 (Reentrant Lock)同一线程可重复获取 (synchronizedReentrantLock)
不可重入锁同一线程重复获取会死锁(Java 中较少见)
公平性公平锁 (Fair Lock)遵循 FIFO,等待时间长的优先获取
非公平锁 (Non-fair Lock)不保证顺序,可能“插队”
共享性独占锁 (Exclusive Lock)写锁,一次只允许一个线程持有
共享锁 (Shared Lock)读锁,允许多个线程同时持有

1. synchronized:JVM 内置锁

✅ 语法与锁对象

public class SynchronizedDemo {
    private final Object lock = new Object();

    // 1. 锁实例对象 (this)
    public synchronized void method1() {
        // ...
    }

    // 2. 锁类对象 (SynchronizedDemo.class)
    public static synchronized void method2() {
        // ...
    }

    // 3. 锁指定对象
    public void method3() {
        synchronized (lock) {
            // ...
        }
    }

    // 4. 锁代码块 (this)
    public void method4() {
        synchronized (this) {
            // ...
        }
    }
}

🔑 关键synchronized 锁的是对象,而不是代码块。

✅ 特性

  • 可重入:同一线程可多次进入同一锁。
  • 自动释放:异常或方法结束时自动释放。
  • 非公平锁:不保证等待线程的顺序。

🔬 synchronized 的锁升级(JDK 1.6+)

为了优化性能,JVM 对 synchronized 进行了深度优化,实现了锁升级:

  1. 偏向锁 (Biased Locking)

    • 当一个线程第一次获取锁时,JVM 会将对象头的 Mark Word 设置为偏向该线程。
    • 后续该线程进入同步块时,只需检查 Mark Word 是否偏向自己,无需同步操作。
    • 优点:极致优化单线程场景。
  2. 轻量级锁 (Lightweight Locking)

    • 当有第二个线程竞争锁时,偏向锁升级为轻量级锁。
    • 使用 CAS 操作将对象头的 Mark Word 替换为指向线程栈中锁记录的指针。
    • 竞争线程通过自旋(循环尝试获取锁)等待,避免线程阻塞。
    • 优点:适用于锁竞争不激烈、持有时间短的场景。
  3. 重量级锁 (Heavyweight Locking)

    • 当自旋一定次数仍未获取锁(或竞争激烈),轻量级锁升级为重量级锁。
    • 依赖操作系统互斥量(Mutex)实现,线程无法获取锁时进入阻塞状态,由操作系统调度。
    • 缺点:线程阻塞/唤醒开销大。

锁升级是单向的:一旦升级为重量级锁,不会降级。


2. ReentrantLock:可重入显式锁

✅ 基本用法

private final ReentrantLock lock = new ReentrantLock();

public void method() {
    lock.lock(); // 获取锁
    try {
        // 临界区代码
    } finally {
        lock.unlock(); // 必须在 finally 中释放!
    }
}

⚠️ 重要unlock() 必须在 finally 块中调用,否则异常时锁无法释放,导致死锁。

✅ 核心优势(相比 synchronized

特性synchronizedReentrantLock
可中断✅ lockInterruptibly()
超时获取✅ tryLock(timeout)
公平锁❌ (非公平)✅ 构造函数可指定
条件变量1个 (wait/notify)✅ 多个 (Condition)
锁状态检查✅ isLocked()getHoldCount()

✅ 示例:可中断与超时

// 1. 可中断获取锁
public void interruptibleMethod() throws InterruptedException {
    lock.lockInterruptibly(); // 可响应中断
    try {
        // ...
    } finally {
        lock.unlock();
    }
}

// 2. 超时获取锁
public boolean timedMethod() {
    try {
        if (lock.tryLock(5, TimeUnit.SECONDS)) {
            try {
                // ...
            } finally {
                lock.unlock();
            }
            return true;
        } else {
            System.out.println("获取锁超时");
            return false;
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return false;
    }
}

🔬 ReentrantLock 的核心:AQS (AbstractQueuedSynchronizer)

  • ReentrantLock 的核心是 AQS 框架。
  • AQS 使用一个 volatile int state 表示同步状态(0=无锁,1=已锁,>1=可重入计数)。
  • 使用 CLH 队列(FIFO 队列)管理等待线程。
  • 获取锁失败的线程会被包装成 Node 加入队列,并通过 LockSupport.park() 阻塞。
  • 释放锁时,唤醒队列中的下一个节点。

AQS 是 JUC 并发包的基石Semaphore, CountDownLatch, ReentrantReadWriteLock 等都基于 AQS 实现。


3. ReentrantReadWriteLock:读写锁

✅ 场景

适用于读多写少的场景,提高并发性能。

  • 读锁 (Shared Lock):允许多个线程同时读。
  • 写锁 (Exclusive Lock):写操作时,其他读写操作均被阻塞。

✅ 用法

private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private Map<String, String> cache = new HashMap<>();

public String get(String key) {
    readLock.lock();
    try {
        return cache.get(key);
    } finally {
        readLock.unlock();
    }
}

public void put(String key, String value) {
    writeLock.lock();
    try {
        cache.put(key, value);
    } finally {
        writeLock.unlock();
    }
}

⚠️ 注意

  • 读锁不能升级为写锁(会导致死锁)。
  • 写锁可以降级为读锁。

4. StampedLock:更高效的读写锁(JDK 8+)

✅ 优势

  • 性能优于 ReentrantReadWriteLock
  • 支持乐观读 (Optimistic Reading):读操作不加锁,最后检查是否有写操作。

✅ 用法

private final StampedLock stampedLock = new StampedLock();
private double x, y;

// 1. 写锁
public void move(double deltaX, double deltaY) {
    long stamp = stampedLock.writeLock();
    try {
        x += deltaX;
        y += deltaY;
    } finally {
        stampedLock.unlockWrite(stamp);
    }
}

// 2. 乐观读
public double distanceFromOrigin() {
    long stamp = stampedLock.tryOptimisticRead(); // 尝试乐观读
    double currentX = x, currentY = y;
    if (!stampedLock.validate(stamp)) { // 检查期间是否有写操作
        stamp = stampedLock.readLock(); // 升级为悲观读锁
        try {
            currentX = x;
            currentY = y;
        } finally {
            stampedLock.unlockRead(stamp);
        }
    }
    return Math.sqrt(currentX * currentX + currentY * currentY);
}

// 3. 悲观读锁
public double readWithReadLock() {
    long stamp = stampedLock.readLock();
    try {
        return Math.sqrt(x * x + y * y);
    } finally {
        stampedLock.unlockRead(stamp);
    }
}

适用场景:读操作非常频繁,且写操作极少。


5. 乐观锁:CAS 与 Atomic 类

✅ 核心:CAS (Compare-And-Swap)

  • CPU 指令级原子操作。
  • 操作:if (value == expected) then value = newValue else retry
  • Java 通过 Unsafe 类实现。

✅ Atomic 类族

  • AtomicIntegerAtomicLongAtomicBoolean
  • AtomicReferenceAtomicStampedReference (解决 ABA 问题)
  • AtomicLongArrayAtomicIntegerFieldUpdater (反射更新)
private AtomicInteger counter = new AtomicInteger(0);

public void increment() {
    counter.incrementAndGet(); // 原子自增
}

public int get() {
    return counter.get();
}

✅ ABA 问题

  • 值从 A → B → A,CAS 认为未变,但中间状态已改变。
  • 解决方案AtomicStampedReference 或 AtomicMarkableReference,引入版本号或标记。

三、锁的性能与选型

性能对比(简要)

锁类型低竞争高竞争说明
synchronized✅✅JDK 1.6+ 优化后性能很好
ReentrantLock✅✅✅✅✅功能丰富,性能略优
ReentrantReadWriteLock✅✅✅ (读)✅ (写)读多写少场景优势明显
StampedLock✅✅✅✅✅✅乐观读性能极高
Atomic 类✅✅✅✅✅✅✅✅✅无锁,高并发计数首选

🔬 JDK 1.6+ 的 synchronized 经过锁升级优化后,性能已接近 ReentrantLock


四、最佳实践

  1. 优先使用 synchronized:简单、安全、JVM 优化好。
  2. 高并发计数用 Atomic 类:无锁,性能高。
  3. 读多写少用 ReentrantReadWriteLock 或 StampedLock
  4. 需要精确控制用 ReentrantLock
  5. 避免锁的过度嵌套,防止死锁。
  6. 缩小锁的粒度,减少临界区代码。
  7. 避免在锁内进行耗时操作(如 I/O、网络调用)。

五、高频问题

❓1. synchronized 锁升级的过程是怎样的?

  1. 无锁 → 2. 偏向锁(偏向首个线程)→ 3. 轻量级锁(CAS + 自旋)→ 4. 重量级锁(Mutex 阻塞)。
    升级是单向的,由 JVM 自动完成。

❓2. ReentrantLock 是如何实现可重入的?


基于 AQS 的 state 变量。
每次成功获取锁,state++;每次释放锁,state--
同一线程可多次获取,state 记录重入次数。


❓3. ReentrantLock 和 synchronized 的区别?

  • 实现synchronized 是 JVM 内置,ReentrantLock 是 API 实现。
  • 功能ReentrantLock 支持中断、超时、公平锁、多个 Condition。
  • 释放synchronized 自动释放,ReentrantLock 需手动释放。
  • 性能:JDK 1.6+ 后性能接近。

❓4. 什么是 AQS?ReentrantLock 如何使用它?


AQS (AbstractQueuedSynchronizer) 是构建锁和同步器的框架。
ReentrantLock 使用 AQS 的 state 表示锁状态,
使用 CLH 队列管理等待线程,
通过 CAS 操作修改 state
获取失败的线程入队并阻塞,释放时唤醒后继。


❓5. StampedLock 的乐观读是如何工作的?

  1. 调用 tryOptimisticRead() 获取一个时间戳 (stamp)
  2. 执行读操作。
  3. 调用 validate(stamp) 检查期间是否有写操作。
  4. 如果 validate 返回 true,读成功;否则需升级为悲观读锁。

六、总结

锁类型推荐度适用场景
synchronized✅✅通用同步,简单场景
ReentrantLock✅✅✅需要高级功能
ReentrantReadWriteLock✅✅✅读多写少
StampedLock✅✅✅✅读极多写极少
Atomic 类✅✅✅✅✅高并发计数/状态

终极建议

  1. 简单同步首选 synchronized
  2. 复杂需求用 ReentrantLock
  3. 读多写少用 StampedLock
  4. 计数用 Atomic
  5. 理解锁的原理(AQS, CAS, 锁升级)是进阶的关键

掌握这些锁机制,你就能在 Java 并发编程的海洋中游刃有余

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值