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
相关推荐
隐藏用户_y7 分钟前
JavaScript闭包概念和应用详解
javascript·面试
我想说一句10 分钟前
CSS 基础知识小课堂:从“选择器”到“声明块”,带你玩转网页的时尚穿搭!
前端·javascript·面试
和雍21 分钟前
”做技术分享?苟都不做“做,做的就是 module.rules 加工过程
javascript·面试·webpack
flzjkl21 分钟前
【Spring】【事务】初学者直呼学会了的Spring事务入门
后端
工呈士25 分钟前
Redux:不可变数据与纯函数的艺术
前端·react.js·面试
aneasystone本尊29 分钟前
使用 OpenMemory MCP 跨客户端共享记忆
后端
花千烬29 分钟前
云原生之Docker, Containerd 与 CRI-O 全面对比
后端
tonydf30 分钟前
还在用旧的认证授权方案?快来试试现代化的OpenIddict!
后端·安全
Wo3Shi4七32 分钟前
消息积压:业务突然增长,导致消息消费不过来怎么办?
后端·kafka·消息队列
江城开朗的豌豆1 小时前
JavaScript篇:移动端点击的300ms魔咒:你以为用户手抖?其实是浏览器在搞事情!
前端·javascript·面试