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
相关推荐
qq_4476630511 分钟前
Spring-注解编程
java·后端·spring
风象南16 分钟前
Redis中5种BitMap应用场景及实现
redis·后端
声声codeGrandMaster2 小时前
Django之modelform使用
后端·python·django
慕容静漪9 小时前
如何本地安装Python Flask并结合内网穿透实现远程开发
开发语言·后端·golang
ErizJ9 小时前
Golang|锁相关
开发语言·后端·golang
软件测试曦曦9 小时前
16:00开始面试,16:08就出来了,问的问题有点变态。。。
自动化测试·软件测试·功能测试·程序人生·面试·职场和发展
烛阴9 小时前
手把手教你搭建 Express 日志系统,告别线上事故!
javascript·后端·express
良许Linux9 小时前
请问做嵌入式开发C语言应该学到什么水平?
后端
拉不动的猪9 小时前
设计模式之------策略模式
前端·javascript·面试
独行soc9 小时前
2025年常见渗透测试面试题-红队面试宝典下(题目+回答)
linux·运维·服务器·前端·面试·职场和发展·csrf