05-Java 锁机制:synchronized、ReentrantLock 与 AQS 全解析

Java 锁机制:synchronized、ReentrantLock 与 AQS 全解析

一、锁的核心概念与分类

1. 为什么需要锁?

  • 竞态条件:多线程并发访问共享资源导致数据不一致
  • 内存可见性:保证线程修改对其他线程立即可见

2. 锁的分类维度

分类标准 锁类型 代表实现
乐观/悲观 乐观锁、悲观锁 CAS vs synchronized
公平性 公平锁、非公平锁 ReentrantLock(true/false)
可重入性 可重入锁、不可重入锁 synchronized
阻塞/非阻塞 阻塞锁、自旋锁 AQS vs CAS

二、synchronized 深度解析

1. 使用方式

java 复制代码
// 实例方法锁
public synchronized void method() {}

// 静态方法锁
public static synchronized void staticMethod() {}

// 代码块锁
synchronized(lockObject) {
    // 临界区
}

2. 底层实现原理

  • Monitor 机制:每个对象关联一个Monitor

    • 对象头Mark Word结构:

      锁状态 存储内容
      无锁 对象hashCode
      偏向锁 线程ID + Epoch
      轻量级锁 指向栈中锁记录的指针
      重量级锁 指向Monitor的指针

3. 锁升级过程

  1. 偏向锁 (单线程访问)
    • 通过CAS设置线程ID
    • 无需同步开销
  2. 轻量级锁 (多线程交替访问)
    • 通过CAS竞争锁记录指针
    • 失败后自旋尝试
  3. 重量级锁 (激烈竞争)
    • 线程阻塞,进入等待队列
    • 依赖操作系统mutex

三、ReentrantLock 机制剖析

1. 核心特性对比

特性 synchronized ReentrantLock
实现机制 JVM内置 JDK实现(AQS)
锁获取方式 自动获取释放 必须手动lock/unlock
可中断 不支持 lockInterruptibly()支持
公平锁 非公平 可配置公平/非公平
条件队列 单条件 多Condition

2. 源码解析(非公平锁实现)

java 复制代码
final void lock() {
    if (compareAndSetState(0, 1)) // 快速尝试获取
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1); // 进入AQS队列
}

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() && 
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires; // 重入计数
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

四、AQS(AbstractQueuedSynchronizer)原理

1. AQS核心结构

java 复制代码
// 关键字段
private volatile int state; // 同步状态
private transient volatile Node head; // CLH队列头
private transient volatile Node tail; // CLH队列尾

// Node节点结构
static final class Node {
    volatile int waitStatus;
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    Node nextWaiter; // 条件队列专用
}

2. 工作流程(以ReentrantLock为例)

  1. 获取锁
    • 尝试CAS修改state
    • 失败后加入CLH队列
    • 自旋检查前驱节点状态
  2. 释放锁
    • 修改state并唤醒后继节点
    • 取消中断标记

3. 关键方法模板

java 复制代码
// 需要子类实现
protected boolean tryAcquire(int arg) { ... }

// AQS提供的模板方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

五、锁的性能优化策略

1. 减少锁竞争的方法

策略 实现方式 适用场景
锁细化 缩小同步代码块范围 大对象的部分字段保护
锁分离 读写锁分离(ReadWriteLock) 读多写少场景
无锁编程 使用Atomic类 简单状态变更
ThreadLocal 线程私有变量 避免共享资源

2. 锁性能对比测试数据

锁类型 吞吐量(ops/ms) 延迟(ns)
无锁 1589 62
偏向锁 1342 74
轻量级锁 987 101
synchronized 563 177
ReentrantLock 621 160

六、锁的常见问题与解决方案

💬 Q1:synchronized和ReentrantLock如何选择?

答案

  • 优先使用synchronized:简单场景、JVM自动优化
  • 选择ReentrantLock:需要高级功能(可中断、公平锁、条件队列)

💬 Q2:什么是锁膨胀?如何避免?

答案

  • 现象:从偏向锁→轻量级锁→重量级锁的升级过程
  • 避免方法
    1. 减少同步代码块执行时间
    2. 降低锁竞争强度(如减小并发线程数)

💬 Q3:AQS为什么用CLH队列?

答案

  1. 前驱节点感知:通过前驱节点的状态减少自旋消耗
  2. 无锁设计:队列操作基于CAS,减少同步开销
  3. 公平性保证:严格FIFO顺序

最佳实践建议

  1. 使用jstack分析锁竞争情况
  2. 避免在锁内调用外部方法(防止死锁)
  3. 高并发场景优先考虑无锁方案(如ConcurrentHashMap
相关推荐
devlei2 小时前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
Accerlator4 小时前
2026 年 4 月 1 日电话面试
面试·职场和发展
努力的小郑4 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
Victor3565 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3565 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁5 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp5 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
宁瑶琴6 小时前
COBOL语言的云计算
开发语言·后端·golang
普通网友7 小时前
阿里云国际版服务器,真的是学生党的性价比之选吗?
后端·python·阿里云·flask·云计算
IT_陈寒8 小时前
Vue的这个响应式问题,坑了我整整两小时
前端·人工智能·后端