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
相关推荐
我是不会赢的9 分钟前
使用 decimal 包解决 go float 浮点数运算失真
开发语言·后端·golang·浮点数
yuqifang25 分钟前
写一个简单的Java示例
java·后端
Re27527 分钟前
分库分表后主键总“撞车”?5种全局唯一ID方案让你不再头疼
后端
拾光拾趣录1 小时前
🔥9种继承写法全解,第7种99%人没用过?⚠️
前端·面试
陈随易1 小时前
VSCode v1.103发布,AI编程任务列表,可用GPT 5和Claude 4.1
前端·后端·程序员
中等生1 小时前
Python的隐形枷锁:GIL如何"绑架"了你的多线程梦想
后端·python
Monika Zhang1 小时前
【面试攻略】回答Java面试问题「挑战与解决方案」技巧
面试·职场和发展
Pitayafruit1 小时前
【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
redis·分布式·后端
哈基米喜欢哈哈哈1 小时前
Netty入门(二)——网络传输
java·开发语言·网络·后端
尘心不灭1 小时前
Spring Boot 项目代码笔记
spring boot·笔记·后端