深入剖析Synchronized锁升级与ReentrantLock/AQS原理

227 阅读3分钟

一、Synchronized锁升级:从偏向锁到重量级锁

1. 锁升级流程

Synchronized的锁状态通过对象头中的Mark Word表示,升级路径如下:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁

2. 重量级锁详解

触发条件

  • 多个线程竞争同一锁。
  • 轻量级锁自旋超过阈值(默认10次,JVM自适应调整)。

实现原理

  1. Monitor对象:每个Java对象关联一个Monitor(由C++实现,位于JVM的ObjectMonitor.hpp)。
  2. 互斥量(Mutex Lock):依赖操作系统提供的互斥量(如Linux的pthread_mutex),涉及用户态到内核态的切换,开销大。
  3. 阻塞与唤醒:竞争失败的线程进入_EntryList队列,通过park()挂起;锁释放时,唤醒队列中的线程。

代码片段(JVM层伪代码)

// ObjectMonitor.hpp
void ObjectMonitor::enter() {
    if (CAS(&_owner, NULL, Self)) {  // 尝试快速获取锁
        return;
    }
    while (true) {
        AddWaiterToEntryList(Self);  // 加入阻塞队列
        Self->_ParkEvent->park();    // 挂起线程(系统调用)
        if (TryAcquire(Self)) break; // 被唤醒后重试获取锁
    }
}

性能问题

  • 上下文切换:每次挂起/唤醒线程需切换CPU模式,耗时约数微秒至毫秒级。
  • 无法自适应:即使竞争短暂,仍可能直接升级为重量级锁。

二、ReentrantLock与AQS:灵活并发的基石

1. ReentrantLock核心特性
  • 可重入:同一线程可重复获取锁。
  • 公平性:支持公平锁(按等待顺序获取)与非公平锁(插队抢锁)。
  • 可中断lockInterruptibly()允许在等待时响应中断。
  • 超时机制tryLock(long timeout, TimeUnit unit)
2. AQS(AbstractQueuedSynchronizer)

形象理解
AQS像一个排队管理器,负责协调线程对共享资源的访问。其核心是一个双向队列(CLH变体),线程通过“取号排队-等待通知”机制获取资源。

内部结构

  • statevolatile int,表示资源状态(如ReentrantLock中表示持有锁的次数)。
  • Node队列:等待线程封装为Node节点,组成FIFO队列。

Node节点关键字段

static final class Node {
    volatile int waitStatus;  // 状态:CANCELLED(1)、SIGNAL(-1)等
    volatile Node prev;       // 前驱节点
    volatile Node next;       // 后继节点
    volatile Thread thread;   // 关联线程
}
3. AQS工作流程(以ReentrantLock为例)

非公平锁加锁流程

final void lock() {
    if (compareAndSetState(0, 1))  // 直接尝试CAS抢锁
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);               // 进入AQS队列
}

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&        // 子类实现(再次尝试获取锁)
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

关键方法解析

  1. tryAcquire()(ReentrantLock实现):

    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()) { // 重入
            setState(c + acquires);
            return true;
        }
        return false;
    }
    
  2. addWaiter():将线程包装为Node并加入队列尾部。

  3. acquireQueued():自旋检查前驱节点是否为头节点,是则尝试获取锁;否则调用LockSupport.park()挂起。

4. AQS的唤醒机制
  • 释放锁:调用unparkSuccessor(Node node),唤醒后继节点线程。
  • 超时/中断:通过cancelAcquire(Node node)将节点状态标记为CANCELLED,并调整队列。

三、Synchronized vs ReentrantLock

特性SynchronizedReentrantLock
实现方式JVM内置,自动管理锁JDK代码,需手动lock()/unlock()
锁类型非公平锁支持公平锁与非公平锁
中断响应不支持支持lockInterruptibly()
条件变量通过wait()/notify()支持多个Condition
性能(低竞争)优(锁升级优化)略优(CAS无阻塞)
性能(高竞争)差(重量级锁开销)优(可配置自旋策略)

四、总结与适用场景

  • Synchronized:适合简单同步场景,代码简洁,JVM自动优化。
  • ReentrantLock:需精细控制(如超时、公平性)、高并发竞争时性能更佳。
  • AQS:作为并发工具基础框架(如Semaphore、CountDownLatch),理解其原理是掌握Java并发的关键。

代码启示:通过AQS的state和队列管理,Java实现了灵活高效的同步机制,而Synchronized的锁升级则体现了JVM对性能的极致优化。