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
相关推荐
zhuiQiuMX11 分钟前
字节面试手撕中等题但还没做出来
面试
争不过朝夕,又念着往昔19 分钟前
Go语言反射机制详解
开发语言·后端·golang
绝无仅有2 小时前
企微审批对接错误与解决方案
后端·算法·架构
Super Rookie2 小时前
Spring Boot 企业项目技术选型
java·spring boot·后端
来自宇宙的曹先生2 小时前
用 Spring Boot + Redis 实现哔哩哔哩弹幕系统(上篇博客改进版)
spring boot·redis·后端
expect7g2 小时前
Flink-Checkpoint-1.源码流程
后端·flink
趣多多代言人2 小时前
从零开始手写嵌入式实时操作系统
开发语言·arm开发·单片机·嵌入式硬件·面试·职场和发展·嵌入式
00后程序员2 小时前
Fiddler中文版如何提升API调试效率:本地化优势与开发者实战体验汇总
后端
用户8122199367223 小时前
C# .Net Core零基础从入门到精通实战教程全集【190课】
后端
bobz9653 小时前
FROM scratch: docker 构建方式分析
后端