JUC并发编程 AQS源码讲解

91 阅读20分钟

AQS源码讲解

前言

AbstractQueuedSynchronizer(AQS)是Java并发包中最核心的基础框架,它为实现阻塞锁和相关同步器提供了一个框架。本文将从ReentrantLock的构造过程开始,逐步深入分析lock和unlock的完整实现机制,揭示Java并发编程的底层奥秘。

1. ReentrantLock构造过程深度解析

1.1 继承关系图

classDiagram
    class Lock {
        <<interface>>
        +lock()
        +unlock()
        +tryLock()
        +newCondition()
    }
    
    class ReentrantLock {
        -Sync sync
        +ReentrantLock()
        +ReentrantLock(boolean fair)
        +lock()
        +unlock()
    }
    
    class Sync {
        <<abstract>>
        +lock()
        +tryRelease(int)
        +isHeldExclusively()
    }
    
    class FairSync {
        +initialTryLock()
        +tryAcquire(int)
    }
    
    class NonfairSync {
        +initialTryLock()
        +tryAcquire(int)
    }
    
    class AbstractQueuedSynchronizer {
        -volatile int state
        -volatile Node head
        -volatile Node tail
        +acquire(int)
        +release(int)
        +getState()
        +setState(int)
        +compareAndSetState(int, int)
    }
    
    class AbstractOwnableSynchronizer {
        -Thread exclusiveOwnerThread
        +setExclusiveOwnerThread(Thread)
        +getExclusiveOwnerThread()
    }
    
    Lock <|.. ReentrantLock
    ReentrantLock *-- Sync
    Sync <|-- FairSync
    Sync <|-- NonfairSync
    AbstractQueuedSynchronizer <|-- Sync
    AbstractOwnableSynchronizer <|-- AbstractQueuedSynchronizer

1.2 ReentrantLock构造函数源码分析

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    
    /** 同步器,提供所有实现机制 */
    private final Sync sync;

    /**
     * 默认构造函数,创建非公平锁
     * 非公平锁性能更好,但可能导致线程饥饿
     * 
     * 选择非公平锁的原因:
     * 1. 更高的吞吐量:减少线程切换开销
     * 2. 更低的延迟:新线程可能立即获取锁
     * 3. 更好的缓存局部性:刚释放锁的线程可能很快重新获取
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * 创建ReentrantLock实例
     * @param fair true表示使用公平锁,false表示非公平锁
     * 
     * 公平锁 vs 非公平锁:
     * - 公平锁:严格按照FIFO顺序,保证公平性,但性能较低
     * - 非公平锁:允许"插队",性能更高,但可能导致线程饥饿
     */
    public ReentrantLock(boolean fair) {
        // 根据参数选择创建公平锁或非公平锁的同步器
        // 这是策略模式的典型应用
        sync = fair ? new FairSync() : new NonfairSync();
    }
}

1.3 Sync基类 - 连接ReentrantLock与AQS的桥梁

/**
 * ReentrantLock的同步器基类,继承自AQS
 * 使用AQS的state字段表示锁的持有次数,支持重入
 * 这是模板方法模式的经典应用:AQS定义框架,Sync实现具体逻辑
 */
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    /**
     * 抽象方法,由子类实现具体的初始获取逻辑
     * 公平锁和非公平锁的核心区别就在这里
     */
    abstract boolean initialTryLock();

    /**
     * 锁的获取入口,模板方法模式
     * 先尝试快速获取,失败则进入AQS的完整流程
     */
    @ReservedStackAccess
    final void lock() {
        if (!initialTryLock())  // 快速尝试获取锁
            acquire(1);         // 进入AQS的完整获取流程
    }

    /**
     * 非公平的tryLock实现
     * 不管队列状态,直接尝试获取锁
     */
    @ReservedStackAccess
    final boolean tryLock() {
        Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 锁空闲,直接CAS获取
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (getExclusiveOwnerThread() == current) {
            // 重入逻辑:当前线程已持有锁
            if (++c < 0) // 检查重入次数溢出
                throw new Error("Maximum lock count exceeded");
            setState(c);
            return true;
        }
        return false;
    }

    /**
     * 释放锁的实现,支持重入锁的递减逻辑
     * 只有当重入计数减到0时,锁才真正被释放
     */
    @ReservedStackAccess
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;  // 计算释放后的状态值
        
        // 安全检查:只有锁的拥有者才能释放锁
        if (getExclusiveOwnerThread() != Thread.currentThread())
            throw new IllegalMonitorStateException();
            
        boolean free = (c == 0);       // 判断是否完全释放
        
        if (free) {
            // 只有完全释放时才清除拥有者线程
            setExclusiveOwnerThread(null);
        }
        
        setState(c);  // 更新状态
        return free;  // 返回是否完全释放
    }

    // 其他辅助方法
    protected final boolean isHeldExclusively() {
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }

    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }

    final boolean isLocked() {
        return getState() != 0;
    }
}

1.4 FairSync - 公平锁实现

/**
 * 公平锁实现
 * 严格按照FIFO顺序,新线程必须排队等待
 * 优势:避免线程饥饿,保证等待时间的公平性
 * 劣势:性能较低,因为需要维护严格的队列顺序
 */
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    /**
     * 公平锁的初始尝试获取锁
     * 与非公平锁的关键区别:必须检查等待队列
     */
    final boolean initialTryLock() {
        Thread current = Thread.currentThread();
        int c = getState();
        
        if (c == 0) {
            // 公平锁的核心:检查是否有其他线程在等待
            // hasQueuedThreads()检查等待队列是否为空
            if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (getExclusiveOwnerThread() == current) {
            // 重入逻辑:即使是公平锁,重入也是允许的
            if (++c < 0)
                throw new Error("Maximum lock count exceeded");
            setState(c);
            return true;
        }
        return false;
    }

    /**
     * 公平锁的tryAcquire实现
     * 与非公平锁的关键区别:必须检查是否有前驱等待者
     */
    protected final boolean tryAcquire(int acquires) {
        if (getState() == 0 && !hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
}

1.5 AbstractQueuedSynchronizer核心字段

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

    /**
     * 等待队列的头节点,延迟初始化
     * 头节点通常代表当前持有锁的线程(或空节点)
     */
    private transient volatile Node head;

    /**
     * 等待队列的尾节点,延迟初始化
     * 新的等待线程总是被添加到队列尾部
     */
    private transient volatile Node tail;

    /**
     * 同步状态,这是AQS的核心
     * 对于ReentrantLock:0表示未锁定,>0表示锁定次数(支持重入)
     * 使用volatile保证可见性,通过CAS保证原子性
     */
    private volatile int state;

    /**
     * 获取同步状态
     */
    protected final int getState() {
        return state;
    }

    /**
     * 设置同步状态
     */
    protected final void setState(int newState) {
        state = newState;
    }

    /**
     * CAS更新同步状态
     * 这是AQS中最关键的原子操作
     */
    protected final boolean compareAndSetState(int expect, int update) {
        return STATE.compareAndSet(this, expect, update);
    }
}

2. ReentrantLock.lock()过程深度解析

2.1 lock()调用链路图

sequenceDiagram
    participant Client
    participant ReentrantLock
    participant FairSync
    participant AQS
    participant Node
    participant LockSupport
    
    Client->>ReentrantLock: lock()
    ReentrantLock->>FairSync: sync.lock()
    FairSync->>FairSync: initialTryLock()
    
    alt 快速获取成功
        FairSync-->>ReentrantLock: 获取成功
        ReentrantLock-->>Client: 返回
    else 快速获取失败
        FairSync->>AQS: acquire(1)
        AQS->>FairSync: tryAcquire(1)
        
        alt tryAcquire成功
            FairSync-->>AQS: true
            AQS-->>FairSync: 获取成功
        else tryAcquire失败
            AQS->>Node: 创建节点
            AQS->>AQS: 入队操作
            AQS->>LockSupport: park()
            Note over LockSupport: 线程阻塞等待
            LockSupport-->>AQS: 被唤醒
            AQS->>AQS: 重新尝试获取
        end
    end

2.2 第一阶段:ReentrantLock.lock()入口

/**
 * ReentrantLock的lock方法
 * 这是用户调用的入口点
 */
public void lock() {
    sync.lock();  // 委托给同步器处理
}

2.3 第二阶段:Sync.lock()模板方法

/**
 * Sync的lock方法,模板方法模式
 * 定义了获取锁的标准流程
 */
@ReservedStackAccess
final void lock() {
    // 第一步:快速路径,尝试立即获取锁
    if (!initialTryLock())  
        // 第二步:慢速路径,进入AQS的完整获取流程
        acquire(1);         
}

2.4 第三阶段:FairSync.initialTryLock()快速获取

/**
 * 公平锁的快速获取尝试
 * 这是性能优化的关键:避免不必要的线程阻塞
 */
final boolean initialTryLock() {
    Thread current = Thread.currentThread();
    int c = getState();  // 获取当前同步状态
    
    if (c == 0) {  // 锁完全空闲
        // 公平锁关键检查:队列是否为空
        if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(current);
            return true;  // 快速获取成功
        }
    } else if (getExclusiveOwnerThread() == current) {
        // 重入情况:当前线程已持有锁
        if (++c < 0)
            throw new Error("Maximum lock count exceeded");
        setState(c);  // 增加重入计数
        return true;
    }
    return false;  // 快速获取失败
}
hasQueuedThreads方法详解

从 tail 开始遍历

  • 目的:安全地检查整个队列是否存在有效节点。

  • 原因

    • 尾部更新是原子的:入队操作通过 CAS 更新 tail 指针,保证 tail 始终指向最新节点。
    • 避免并发入队干扰:从 tail 向 head 遍历时,新节点只会添加到尾部,不影响正向遍历的稳定性。
    • 规避 next 指针延迟:节点入队时先设置 prev 指针,再设置 next 指针。反向遍历依赖 prev,能更快看到新节点。
/**
 * 检查是否有线程在等待队列中排队
 * 这是公平锁判断是否需要排队的关键方法
 * 
 * 实现原理:
 * 1. 从tail向head遍历队列
 * 2. 检查每个节点的status是否>=0(非取消状态)
 * 3. 如果找到任何有效等待节点,返回true
 * 
 * 性能优化:
 * - 使用局部变量缓存head和tail,减少volatile读取
 * - 短路求值:一旦找到有效节点立即返回
 */
public final boolean hasQueuedThreads() {
    // 从尾部开始遍历,直到头部或遇到null
    for (Node p = tail, h = head; p != h && p != null; p = p.prev) {
        // status >= 0 表示节点未被取消
        // CANCELLED = 0x80000000 是负数
        // WAITING = 1, 其他正常状态都是非负数
        if (p.status >= 0)
            return true;  // 发现有效等待节点
    }
    return false;  // 队列为空或所有节点都已取消
}

2.5 第四阶段:AQS.acquire()完整获取流程

/**
 * AQS的acquire方法 - 独占模式获取的入口
 * 这是所有独占锁获取的统一入口
 * 
 * 设计思路:
 * 1. 先给子类一次快速获取的机会(tryAcquire)
 * 2. 如果失败,进入完整的排队等待流程
 * 3. 这种两阶段设计平衡了性能和公平性
 */
public final void acquire(int arg) {
    // 阶段1:快速尝试获取锁(给子类第二次机会)
    // 这里的tryAcquire与initialTryLock可能有不同的实现策略
    if (!tryAcquire(arg))  
        // 阶段2:进入完整的获取流程:创建节点、排队、阻塞、唤醒
        // 参数说明:node=null, arg=1, shared=false, interruptible=false, timed=false, time=0L
        acquire(null, arg, false, false, false, 0L);  
}

2.6 第五阶段:FairSync.tryAcquire()第二次尝试

/**
 * 公平锁的第二次获取尝试
 * 在即将进入队列前的最后一次尝试
 */
protected final boolean tryAcquire(int acquires) {
    // 双重检查:锁空闲 && 没有前驱等待者
    if (getState() == 0 && !hasQueuedPredecessors() &&
        compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}
hasQueuedPredecessors方法详解
从 head 开始遍历
  • 目的:高效获取第一个有效节点(head.next)。

  • 原因

    • 多数情况下,head.next 能直接定位到第一个等待线程。

    • 但需处理三种并发异常(见代码注释):

      • s == null:节点正被移除。
      • first == null:节点未完全初始化(waiter 未赋值)。
      • s.prev == nullprev 指针未正确链接。
    • 快速路径失败时,需降级到慢速路径(从 tail 遍历)  保证正确性。

/**
 * 检查是否有其他线程比当前线程更早地在队列中等待
 * 这是公平锁实现FIFO顺序的核心方法
 * 
 * 设计思路:
 * 1. 快速路径:直接检查head.next节点
 * 2. 慢速路径:如果快速检查失败,通过getFirstQueuedThread确认
 * 3. 最终判断:第一个等待线程是否为当前线程
 * 
 * 返回值含义:
 * - true: 有其他线程在当前线程之前等待,当前线程应该排队
 * - false: 当前线程可以尝试获取锁(队列为空或当前线程是第一个)
 */
public final boolean hasQueuedPredecessors() {
    Thread first = null; 
    Node h, s;
    
    // 快速路径检查:head -> head.next 链路
    if ((h = head) != null && 
        ((s = h.next) == null ||           // head.next为null(队列正在变化)
         (first = s.waiter) == null ||     // head.next.waiter为null(节点未完全初始化)
         s.prev == null)) {                // head.next.prev为null(节点正在入队)
        
        // 快速路径失败,使用慢速路径确认
        // 这种情况通常发生在并发修改队列时
        first = getFirstQueuedThread();
    }
    
    // 判断第一个等待线程是否为当前线程
    // first != null: 队列中有等待线程
    // first != Thread.currentThread(): 第一个等待线程不是当前线程
    return first != null && first != Thread.currentThread();
}

getFirstQueuedThread方法详解

/**
 * 获取队列中第一个(等待时间最长的)线程
 * 这是hasQueuedPredecessors的后备方法
 * 
 * 实现策略:
 * 1. 优先使用head.next快速获取
 * 2. 如果快速路径失败,从tail向前遍历
 * 
 * 为什么需要从tail遍历:
 * - 入队是原子的(CAS tail),但设置next链接不是
 * - 在并发环境下,head.next可能暂时不可用
 * - 从tail向前遍历能保证找到真正的第一个等待线程
 */
public final Thread getFirstQueuedThread() {
    Thread first = null, w; 
    Node h, s;
    
    // 尝试快速路径:head -> head.next
    if ((h = head) != null && 
        ((s = h.next) == null ||           // next链接未建立
         (first = s.waiter) == null ||     // waiter未设置
         s.prev == null)) {                // prev链接异常
        
        // 快速路径失败,使用慢速路径
        // 从tail开始向前遍历,找到最前面的有效节点
        for (Node p = tail, q; p != null && (q = p.prev) != null; p = q) {
            if ((w = p.waiter) != null)
                first = w;  // 不断更新first,最终得到最前面的线程
        }
    }
    return first;
}

2.7 第六阶段:AQS核心acquire方法详解

这是AQS中最复杂也是最核心的方法,实现了完整的线程同步机制。让我们逐行分析其精妙的设计:

2.7.1 acquire方法
/**
 * AQS的核心acquire方法 - 完整版本
 * 这个方法是整个AQS框架的心脏,实现了:
 * 1. 线程安全的队列管理
 * 2. 高效的自旋等待机制
 * 3. 精确的线程阻塞和唤醒
 * 4. 完善的异常和中断处理
 * 
 * @param node 当前线程的节点(初始为null,延迟创建)
 * @param arg 获取参数(对于ReentrantLock通常是1)
 * @param shared 是否为共享模式(false表示独占模式)
 * @param interruptible 是否响应中断
 * @param timed 是否有超时限制
 * @param time 超时时间(纳秒)
 * @return 1表示成功,负数表示失败或中断
 */
final int acquire(Node node, int arg, boolean shared,
                  boolean interruptible, boolean timed, long time) {
    // 获取当前线程引用,避免重复调用
    Thread current = Thread.currentThread();
    
    // 自旋计数器:控制自旋次数,避免过度消耗CPU
    byte spins = 0, postSpins = 0;
    
    // 状态标记
    boolean interrupted = false;  // 是否被中断过
    boolean first = false;        // 是否是队列中第一个等待者
    
    // 前驱节点引用,用于队列遍历和状态检查
    Node pred = null;

    /*
     * 核心无限循环:实现状态机驱动的获取逻辑
     * 
     * 状态转换路径:
     * 1. 初始状态 -> 检查获取资格
     * 2. 有资格 -> 尝试获取锁
     * 3. 获取成功 -> 更新队列并返回
     * 4. 获取失败 -> 创建/入队节点
     * 5. 节点入队 -> 自旋等待
     * 6. 自旋结束 -> 设置等待状态
     * 7. 状态设置 -> 阻塞等待
     * 8. 被唤醒 -> 回到步骤1
     * 
     * 这种设计的优势:
     * - 无锁化:整个过程不需要额外的锁保护
     * - 高效性:通过自旋减少线程切换
     * - 公平性:严格按照FIFO顺序
     * - 健壮性:完善的异常和边界情况处理
     */
    for (;;) {
        // =================== 阶段1:获取资格检查 ===================
        /*
         * 这个阶段决定当前线程是否有资格尝试获取锁
         * 核心逻辑:只有队列中的第一个等待者才能尝试获取锁
         * 
         * 条件分析:
         * !first: 当前不是第一个等待者
         * pred = node.prev: 获取前驱节点
         * !(first = (head == pred)): 检查前驱是否为头节点
         */
        if (!first && (pred = (node == null) ? null : node.prev) != null &&
            !(first = (head == pred))) {
            
            // 前驱节点已被取消,需要清理队列
            if (pred.status < 0) {
                cleanQueue();           // 批量清理取消的节点
                continue;               // 重新开始循环
            } 
            // 前驱节点正在入队过程中(prev链接未完成)
            else if (pred.prev == null) {
                Thread.onSpinWait();    // CPU友好的自旋等待
                continue;               // 等待前驱完成入队
            }
        }
        // =================== 阶段2:尝试获取锁 ===================
        /*
         * 只有满足以下条件之一才能尝试获取:
         * 1. first = true: 是队列中第一个等待者
         * 2. pred == null: 还没有入队(首次尝试)
         * 
         * 这种设计保证了公平性:只有轮到的线程才能获取锁
         */
        if (first || pred == null) {
            boolean acquired;
            try {
                // 调用子类实现的获取逻辑
                // 对于ReentrantLock,这里会调用FairSync.tryAcquire()
                acquired = tryAcquire(arg);
            } catch (Throwable ex) {
                // 异常处理:取消当前节点并重新抛出异常
                cancelAcquire(node, interrupted, false);
                throw ex;
            }
            
            if (acquired) {
                // ============ 获取成功的后续处理 ============
                if (first) {
                    /*
                     * 更新队列头节点:
                     * 1. 将当前节点设为新的头节点
                     * 2. 清理节点的等待信息
                     * 3. 断开与前驱的链接
                     * 4. 帮助GC回收旧的头节点
                     */
                    node.prev = null;           // 断开前驱链接
                    head = node;                // 更新头节点
                    pred.next = null;           // 断开旧头节点的后继链接
                    node.waiter = null;         // 清除等待线程(头节点不需要)
                    
                    // 如果在等待过程中被中断过,恢复中断状态
                    if (interrupted)
                        current.interrupt();
                }
                return 1;  // 成功获取,返回正数
            }
        }
        // =================== 阶段3:节点创建和入队 ===================
        
        // 子阶段3.1:延迟创建节点
        if (node == null) {
            /*
             * 延迟创建策略:
             * 只有在确实需要等待时才创建节点
             * 这避免了不必要的对象分配,提高了快速路径的性能
             */
            if (shared)
                node = new SharedNode();    // 共享模式节点
            else
                node = new ExclusiveNode();  // 独占模式节点(ReentrantLock使用)
        } 
        // 子阶段3.2:节点入队操作
        else if (pred == null) {
            /*
             * 入队操作的精妙设计:
             * 1. 先设置waiter,确保节点有效
             * 2. 使用Relaxed模式设置prev,减少内存屏障开销
             * 3. CAS更新tail,保证原子性
             * 4. 成功后设置next链接,完成双向链表
             */
            node.waiter = current;          // 设置等待线程
            Node t = tail;                  // 获取当前尾节点
            node.setPrevRelaxed(t);         // 设置前驱(Relaxed模式)
            
            if (t == null) {
                // 队列为空,需要初始化头节点
                tryInitializeHead();
            } else if (!casTail(t, node)) {
                // CAS失败,说明有其他线程在并发入队
                node.setPrevRelaxed(null);  // 重置prev,准备重试
            } else {
                // CAS成功,完成入队
                t.next = node;              // 设置前驱的next链接
            }
        }
        // =================== 阶段4:自旋优化 ===================
        /*
         * 自旋策略:
         * 对于队列中第一个等待者,进行短暂自旋
         * 目的:如果锁很快被释放,可以避免线程阻塞的开销
         * 
         * 自旋条件:
         * 1. first = true: 是第一个等待者
         * 2. spins != 0: 还有剩余自旋次数
         */
        else if (first && spins != 0) {
            --spins;                    // 减少自旋计数
            Thread.onSpinWait();        // CPU友好的自旋指令
            /*
             * Thread.onSpinWait()的作用:
             * 1. 向CPU发出自旋等待的提示
             * 2. 在某些架构上可以降低功耗
             * 3. 避免流水线停顿,提高性能
             */
        }
        // =================== 阶段5:设置等待状态 ===================
        /*
         * 状态设置策略:
         * 在阻塞线程之前,必须先设置节点状态为WAITING
         * 这样其他线程在释放锁时才知道需要唤醒这个节点
         * 
         * 状态值含义:
         * 0: 初始状态或已取消
         * WAITING(1): 需要被唤醒
         * CANCELLED(负数): 已取消
         */
        else if (node.status == 0) {
            node.status = WAITING;      // 设置为等待状态
            /*
             * 为什么不直接在创建节点时设置WAITING?
             * 1. 避免在自旋阶段被误唤醒
             * 2. 确保只有真正需要阻塞的节点才设置WAITING
             * 3. 简化状态管理逻辑
             */
        }
        // =================== 阶段6:线程阻塞 ===================
        /*
         * 最终阶段:阻塞当前线程等待唤醒
         * 这是性能的关键点,需要精确控制阻塞和唤醒
         */
        else {
            long nanos;
            
            // 更新自旋计数器(用于被唤醒后的自旋)
            spins = postSpins = (byte)((postSpins << 1) | 1);
            
            if (!timed) {
                // 无超时限制:无限期阻塞
                LockSupport.park(this);
                /*
                 * LockSupport.park()的特点:
                 * 1. 比Object.wait()更高效
                 * 2. 不需要获取监视器锁
                 * 3. 支持精确的线程唤醒
                 * 4. 可以被unpark()提前唤醒
                 */
            } else if ((nanos = time - System.nanoTime()) > 0L) {
                // 有超时限制:定时阻塞
                LockSupport.parkNanos(this, nanos);
            } else {
                // 超时,退出循环
                break;
            }
            
            // 被唤醒后的处理
            node.clearStatus();         // 清除WAITING状态
            
            // 检查中断状态
            if ((interrupted |= Thread.interrupted()) && interruptible) {
                break;  // 响应中断,退出循环
            }
            
            /*
             * 线程被唤醒的可能原因:
             * 1. 前驱节点释放了锁(正常情况)
             * 2. 被中断
             * 3. 超时
             * 4. 虚假唤醒(spurious wakeup)
             * 
             * 无论哪种情况,都会重新进入循环,
             * 重新检查获取条件
             */
        }
    }  // for循环结束
    
    // 如果到达这里,说明获取失败(中断或超时)
    return cancelAcquire(node, interrupted, interruptible);
}
2.7.9 设计精髓总结

这个方法的设计体现了以下几个重要思想:

  1. 状态机驱动:通过无限循环实现状态转换,每个阶段都有明确的职责
  2. 延迟初始化:节点和队列都是延迟创建,提高快速路径性能
  3. 自旋优化:通过适度自旋减少线程切换开销
  4. 内存屏障优化:使用Relaxed模式减少不必要的内存屏障
  5. 异常安全:完善的异常处理和资源清理机制
  6. 公平性保证:严格的FIFO顺序,避免线程饥饿
  7. 性能平衡:在公平性和性能之间找到最佳平衡点

2.8 AQS内存结构图

graph TB
    subgraph "AQS内存结构"
        AQS["AbstractQueuedSynchronizer<br/>state: int (锁状态)<br/>head: Node (队列头)<br/>tail: Node (队列尾)"]
        
        subgraph "CLH双向链表队列"
            HEAD["Head Node (虚拟节点)<br/>status: 0<br/>waiter: null<br/>prev: null<br/>next: Node1"]
            NODE1["Node1<br/>status: WAITING(1)<br/>waiter: Thread1<br/>prev: head<br/>next: Node2"]
            NODE2["Node2<br/>status: WAITING(1)<br/>waiter: Thread2<br/>prev: Node1<br/>next: Node3"]
            NODE3["Node3 (Tail)<br/>status: WAITING(1)<br/>waiter: Thread3<br/>prev: Node2<br/>next: null"]
            
            HEAD -->|next| NODE1
            NODE1 -->|next| NODE2
            NODE2 -->|next| NODE3
            
            NODE1 -.->|prev| HEAD
            NODE2 -.->|prev| NODE1
            NODE3 -.->|prev| NODE2
        end
        
        AQS -->|head指针| HEAD
        AQS -->|tail指针| NODE3
    end
    
    subgraph "状态说明"
        STATE1["state = 0: 锁空闲"]
        STATE2["state > 0: 锁被持有,值为重入次数"]
        STATUS1["WAITING = 1: 需要被唤醒"]
        STATUS2["CANCELLED = 0x80000000: 已取消"]
    end
    
    subgraph "队列特性"
        FEATURE1["✓ 双向链表:支持正向和反向遍历"]
        FEATURE2["✓ FIFO顺序:先入队的线程先被唤醒"]
        FEATURE3["✓ 虚拟头节点:简化边界条件处理"]
        FEATURE4["✓ 原子操作:CAS保证线程安全"]
    end
    
    style AQS fill:#e3f2fd
    style HEAD fill:#f5f5f5
    style NODE1 fill:#ffecb3
    style NODE2 fill:#ffecb3
    style NODE3 fill:#ffcdd2
    style STATE1 fill:#e8f5e8
    style STATE2 fill:#e8f5e8
    style STATUS1 fill:#fff3e0
    style STATUS2 fill:#fff3e0
    style FEATURE1 fill:#f3e5f5
    style FEATURE2 fill:#f3e5f5
    style FEATURE3 fill:#f3e5f5
    style FEATURE4 fill:#f3e5f5

3. ReentrantLock.unlock()过程深度解析

3.1 unlock()调用链路图

sequenceDiagram
    participant Client
    participant ReentrantLock
    participant Sync
    participant AQS
    participant Node
    participant LockSupport
    
    Client->>ReentrantLock: unlock()
    ReentrantLock->>AQS: sync.release(1)
    AQS->>Sync: tryRelease(1)
    
    alt 重入情况(state > 1)
        Sync->>Sync: state--
        Sync-->>AQS: false (未完全释放)
        AQS-->>ReentrantLock: 完成
    else 完全释放(state == 1)
        Sync->>Sync: state = 0
        Sync->>Sync: setExclusiveOwnerThread(null)
        Sync-->>AQS: true (完全释放)
        AQS->>AQS: signalNext(head)
        AQS->>Node: 找到后继节点
        AQS->>Node: 清除WAITING状态
        AQS->>LockSupport: unpark(successor.waiter)
        Note over LockSupport: 唤醒等待线程
        LockSupport-->>Node: 线程被唤醒
        Node->>AQS: 重新进入acquire循环
    end

3.2 第一阶段:ReentrantLock.unlock()入口

/**
 * ReentrantLock的unlock方法
 * 用户调用的解锁入口点
 */
public void unlock() {
    sync.release(1);  // 委托给AQS的release方法
}

3.3 第二阶段:AQS.release()释放流程

/**
 * AQS的release方法 - 独占模式释放
 * 如果tryRelease返回true,则唤醒等待队列中的下一个线程
 * 
 * 执行流程:
 * 1. 调用子类的tryRelease尝试释放锁
 * 2. 如果完全释放(重入计数为0),唤醒等待队列中的下一个线程
 * 3. 如果未完全释放(重入计数>0),直接返回false
 * 
 * 重入锁的释放特点:
 * - 每次unlock只减少1个重入计数
 * - 只有当计数减到0时才真正释放锁
 * - 只有真正释放时才会唤醒其他等待线程
 */
public final boolean release(int arg) {
    // 阶段1:尝试释放锁(处理重入计数)
    if (tryRelease(arg)) {      
        // 阶段2:锁完全释放,唤醒等待队列中的下一个线程
        // 从头节点开始查找并唤醒第一个有效的等待节点
        signalNext(head);
        return true;  // 释放成功
    }
    return false;  // 锁未完全释放(重入情况,计数>0)
}

3.4 第三阶段:Sync.tryRelease()释放逻辑

/**
 * 释放锁的核心实现
 * 处理重入锁的递减逻辑
 */
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;  // 计算释放后的状态
    
    // 安全检查:只有锁的拥有者才能释放
    if (getExclusiveOwnerThread() != Thread.currentThread())
        throw new IllegalMonitorStateException();
        
    boolean free = (c == 0);  // 判断是否完全释放
    
    if (free) {
        // 只有完全释放时才清除拥有者线程
        // 这确保了重入情况下线程仍然是锁的拥有者
        setExclusiveOwnerThread(null);
    }
    
    setState(c);  // 更新状态(不需要CAS,因为只有拥有者能调用)
    return free;  // 返回是否完全释放
}

3.5 第四阶段:AQS.signalNext()唤醒后继

/**
 * 唤醒后继节点的核心方法
 * 实现了精确的线程唤醒机制,避免"惊群效应"
 * 
 * 设计要点:
 * 1. 只唤醒直接后继节点,不是广播唤醒
 * 2. 使用原子操作确保状态更新的线程安全性
 * 3. 通过LockSupport实现精确的线程唤醒
 * 
 * 状态检查逻辑:
 * - status = 0: 节点已被取消或正常状态,无需唤醒
 * - status = WAITING(1): 节点正在等待,需要被唤醒
 * - status = CANCELLED(负数): 节点已取消,无需唤醒
 */
static void signalNext(Node h) {
    Node s;
    // 三重检查确保唤醒的有效性:
    if (h != null &&                    // 1. 头节点存在
        (s = h.next) != null &&         // 2. 后继节点存在
        s.status != 0) {                // 3. 后继节点需要被唤醒(status != 0)
        
        // 阶段1:原子性地清除WAITING状态,防止重复唤醒
        // getAndUnsetStatus是原子操作,返回旧值并清除指定位
        s.getAndUnsetStatus(WAITING);
        
        // 阶段2:唤醒后继节点中的等待线程
        // LockSupport.unpark是线程唤醒的底层原语
        LockSupport.unpark(s.waiter);
    }
}

3.6 unlock过程状态变化图

stateDiagram-v2
    [*] --> 持有锁状态
    
    持有锁状态 : state > 0
    持有锁状态 : exclusiveOwnerThread = currentThread
    
    持有锁状态 --> 检查重入: unlock()调用
    
    检查重入 --> 减少重入计数: state > 1 (重入情况)
    检查重入 --> 完全释放: state == 1 (最后一次释放)
    
    减少重入计数 : state--
    减少重入计数 : 保持exclusiveOwnerThread
    减少重入计数 --> 持有锁状态: 继续持有锁
    
    完全释放 : state = 0
    完全释放 : exclusiveOwnerThread = null
    完全释放 --> 唤醒后继: signalNext(head)
    
    唤醒后继 --> 锁空闲状态: 唤醒完成
    
    锁空闲状态 : state = 0
    锁空闲状态 : 等待新的获取者
    锁空闲状态 --> [*]

4. 线程竞争场景模拟 - AQS状态变化全过程

为了更好地理解AQS的工作机制,我们来模拟一个典型的多线程竞争场景:

  • 线程A首先获取锁
  • 线程B尝试获取锁(被阻塞)
  • 线程A释放锁
  • 线程B被唤醒并获取锁

4.1 初始状态

graph TB
    subgraph "AQS初始状态"
        AQS1["AQS<br/>state: 0<br/>head: null<br/>tail: null<br/>exclusiveOwnerThread: null"]
    end
    
    subgraph "线程状态"
        ThreadA["线程A: 准备lock()"]
        ThreadB["线程B: 准备lock()"]
    end
    
    style AQS1 fill:#e1f5fe
    style ThreadA fill:#fff3e0
    style ThreadB fill:#fff3e0

4.2 阶段1:线程A获取锁成功

执行流程:

  1. 线程A调用lock.lock()
  2. 进入FairSync.initialTryLock()
  3. getState() 返回 0(锁空闲)
  4. hasQueuedThreads() 返回 false(队列为空)
  5. compareAndSetState(0, 1) 成功
  6. setExclusiveOwnerThread(ThreadA)
graph TB
    subgraph "AQS状态 - 线程A获取锁后"
        AQS2["AQS<br/>state: 1<br/>head: null<br/>tail: null<br/>exclusiveOwnerThread: ThreadA"]
    end
    
    subgraph "线程状态"
        ThreadA2["线程A: 持有锁,执行临界区"]
        ThreadB2["线程B: 准备lock()"]
    end
    
    style AQS2 fill:#c8e6c9
    style ThreadA2 fill:#4caf50,color:#fff
    style ThreadB2 fill:#fff3e0

4.3 阶段2:线程B尝试获取锁,进入等待队列

执行流程:

  1. 线程B调用lock.lock()
  2. 进入FairSync.initialTryLock()
  3. getState() 返回 1(锁被占用)
  4. getExclusiveOwnerThread() != ThreadB,快速获取失败
  5. 进入AQS.acquire(1)
  6. FairSync.tryAcquire(1) 失败(锁被占用)
  7. 创建Node节点,入队操作
  8. 线程B被LockSupport.park()阻塞
graph LR
    subgraph "AQS状态 - 线程B入队后"
        AQS3["AQS<br/>state: 1<br/>exclusiveOwnerThread: ThreadA"]
        
        subgraph "CLH队列"
            HEAD3["Head Node<br/>status: 0<br/>waiter: null<br/>prev: null"]
            NODE3["Node (ThreadB)<br/>status: WAITING(1)<br/>waiter: ThreadB<br/>prev: head<br/>next: null"]
            
            HEAD3 -->|next| NODE3
            NODE3 -.->|prev| HEAD3
        end
        
        AQS3 -->|head| HEAD3
        AQS3 -->|tail| NODE3
    end
    
    subgraph "线程状态"
        ThreadA3["线程A: 持有锁,执行临界区"]
        ThreadB3["线程B: BLOCKED (LockSupport.park)"]
    end
    
    subgraph "指针说明"
        PTR1["head指针 -> 队列头部(虚拟节点)"]
        PTR2["tail指针 -> 队列尾部(最后入队节点)"]
        PTR3["next指针 -> 正向链接"]
        PTR4["prev指针 -> 反向链接"]
    end
    
    style AQS3 fill:#ffecb3
    style HEAD3 fill:#e0e0e0
    style NODE3 fill:#ffcdd2
    style ThreadA3 fill:#4caf50,color:#fff
    style ThreadB3 fill:#f44336,color:#fff
    style PTR1 fill:#e3f2fd
    style PTR2 fill:#e3f2fd
    style PTR3 fill:#f3e5f5
    style PTR4 fill:#f3e5f5

4.4 阶段3:线程A释放锁

执行流程:

  1. 线程A调用lock.unlock()
  2. 进入AQS.release(1)
  3. Sync.tryRelease(1) 执行:
    • getState() - 1 = 0
    • setExclusiveOwnerThread(null)
    • setState(0)
    • 返回 true(锁完全释放)
  4. signalNext(head) 执行:
    • 找到head.next节点(ThreadB的节点)
    • getAndUnsetStatus(WAITING) 清除WAITING状态
    • LockSupport.unpark(ThreadB) 唤醒线程B
graph LR
    subgraph "AQS状态 - 线程A释放锁后"
        AQS4["AQS<br/>state: 0<br/>exclusiveOwnerThread: null"]
        
        subgraph "CLH队列"
            HEAD4["Head Node<br/>status: 0<br/>waiter: null<br/>prev: null"]
            NODE4["Node (ThreadB)<br/>status: 0 (已清除WAITING)<br/>waiter: ThreadB<br/>prev: head<br/>next: null"]
            
            HEAD4 -->|next| NODE4
            NODE4 -.->|prev| HEAD4
        end
        
        AQS4 -->|head| HEAD4
        AQS4 -->|tail| NODE4
    end
    
    subgraph "线程状态"
        ThreadA4["线程A: 释放锁,退出临界区"]
        ThreadB4["线程B: 被唤醒,准备重新尝试获取锁"]
    end
    
    subgraph "唤醒过程"
        SIGNAL1["1. signalNext(head)"]
        SIGNAL2["2. 找到head.next节点"]
        SIGNAL3["3. getAndUnsetStatus(WAITING)"]
        SIGNAL4["4. LockSupport.unpark(ThreadB)"]
        
        SIGNAL1 --> SIGNAL2
        SIGNAL2 --> SIGNAL3
        SIGNAL3 --> SIGNAL4
    end
    
    style AQS4 fill:#e8f5e8
    style HEAD4 fill:#e0e0e0
    style NODE4 fill:#fff9c4
    style ThreadA4 fill:#9e9e9e
    style ThreadB4 fill:#ff9800,color:#fff
    style SIGNAL1 fill:#e1f5fe
    style SIGNAL2 fill:#e1f5fe
    style SIGNAL3 fill:#e1f5fe
    style SIGNAL4 fill:#e1f5fe

4.5 阶段4:线程B被唤醒,获取锁成功

执行流程:

  1. 线程B从LockSupport.park()中被唤醒
  2. 重新进入acquire方法的循环
  3. 检查first = (head == pred),发现自己是第一个等待者
  4. 调用tryAcquire(1)
    • getState() == 0(锁已释放)
    • !hasQueuedPredecessors()(自己就是第一个)
    • compareAndSetState(0, 1) 成功
    • setExclusiveOwnerThread(ThreadB)
  5. 更新head指针,将自己的节点设为新的head
  6. 清理前驱节点
graph LR
    subgraph "AQS状态 - 线程B获取锁后"
        AQS5["AQS<br/>state: 1<br/>exclusiveOwnerThread: ThreadB"]
        
        subgraph "CLH队列"
            HEAD5["Head Node (原ThreadB节点)<br/>status: 0<br/>waiter: null (已清除)<br/>prev: null<br/>next: null"]
        end
        
        AQS5 -->|head| HEAD5
        AQS5 -->|tail| HEAD5
    end
    
    subgraph "线程状态"
        ThreadA5["线程A: 已完成"]
        ThreadB5["线程B: 持有锁,执行临界区"]
    end
    
    subgraph "队列变化过程"
        CHANGE1["1. ThreadB获取锁成功"]
        CHANGE2["2. 将ThreadB节点设为新head"]
        CHANGE3["3. 清除waiter引用"]
        CHANGE4["4. head和tail都指向同一节点"]
        CHANGE5["5. 队列回到单节点状态"]
        
        CHANGE1 --> CHANGE2
        CHANGE2 --> CHANGE3
        CHANGE3 --> CHANGE4
        CHANGE4 --> CHANGE5
    end
    
    style AQS5 fill:#c8e6c9
    style HEAD5 fill:#e0e0e0
    style ThreadA5 fill:#e0e0e0
    style ThreadB5 fill:#4caf50,color:#fff
    style CHANGE1 fill:#e8f5e8
    style CHANGE2 fill:#e8f5e8
    style CHANGE3 fill:#e8f5e8
    style CHANGE4 fill:#e8f5e8
    style CHANGE5 fill:#e8f5e8

4.6 关键状态变化总结

stateDiagram-v2
    [*] --> 锁空闲: 初始状态<br/>state=0, head=null
    
    锁空闲 --> A持有锁: ThreadA.lock()<br/>state=1, owner=A
    
    A持有锁 --> A持有锁_B等待: ThreadB.lock()<br/>创建队列, B入队阻塞
    
    A持有锁_B等待 --> 锁空闲_B唤醒: ThreadA.unlock()<br/>state=0, 唤醒B
    
    锁空闲_B唤醒 --> B持有锁: ThreadB获取锁<br/>state=1, owner=B
    
    B持有锁 --> [*]: ThreadB.unlock()
    
    note right of A持有锁_B等待
        队列状态:
        head -> dummy
        tail -> NodeB(WAITING)
    end note
    
    note right of 锁空闲_B唤醒
        关键操作:
        1. signalNext(head)
        2. LockSupport.unpark(B)
        3. B重新进入acquire循环
    end note

4.7 内存可见性保证

在整个过程中,AQS通过以下机制保证内存可见性:

  1. volatile字段stateheadtail都是volatile,保证跨线程可见
  2. CAS操作:具有内存屏障语义,保证操作的原子性和可见性
  3. LockSupport:park/unpark操作具有内存同步语义
  4. happens-before关系:unlock操作happens-before后续的lock操作

4.8 复杂竞争场景:多线程等待队列

为了更好地理解tail指针的重要性,我们来看一个更复杂的场景:线程A持有锁,线程B、C、D依次尝试获取锁。

graph LR
    subgraph "多线程竞争场景 - 完整队列状态"
        AQS6["AQS<br/>state: 1<br/>exclusiveOwnerThread: ThreadA"]
        
        subgraph "CLH等待队列"
            HEAD6["Head Node (虚拟)<br/>status: 0<br/>waiter: null<br/>prev: null<br/>next: NodeB"]
            NODEB["NodeB<br/>status: WAITING(1)<br/>waiter: ThreadB<br/>prev: head<br/>next: NodeC"]
            NODEC["NodeC<br/>status: WAITING(1)<br/>waiter: ThreadC<br/>prev: NodeB<br/>next: NodeD"]
            NODED["NodeD (Tail)<br/>status: WAITING(1)<br/>waiter: ThreadD<br/>prev: NodeC<br/>next: null"]
            
            HEAD6 -->|next| NODEB
            NODEB -->|next| NODEC
            NODEC -->|next| NODED
            
            NODEB -.->|prev| HEAD6
            NODEC -.->|prev| NODEB
            NODED -.->|prev| NODEC
        end
        
        AQS6 -->|head指针| HEAD6
        AQS6 -->|tail指针| NODED
    end
    
    subgraph "线程状态"
        ThreadA6["线程A: 持有锁"]
        ThreadB6["线程B: BLOCKED"]
        ThreadC6["线程C: BLOCKED"]
        ThreadD6["线程D: BLOCKED"]
    end
    
    subgraph "关键操作说明"
        OP1["入队:新线程总是添加到tail后"]
        OP2["出队:从head.next开始唤醒"]
        OP3["遍历:hasQueuedThreads从tail向head"]
        OP4["查找:getFirstQueuedThread优先head.next"]
    end
    
    style AQS6 fill:#ffecb3
    style HEAD6 fill:#f5f5f5
    style NODEB fill:#ffcdd2
    style NODEC fill:#ffcdd2
    style NODED fill:#f8bbd9
    style ThreadA6 fill:#4caf50,color:#fff
    style ThreadB6 fill:#f44336,color:#fff
    style ThreadC6 fill:#f44336,color:#fff
    style ThreadD6 fill:#f44336,color:#fff
    style OP1 fill:#e1f5fe
    style OP2 fill:#e1f5fe
    style OP3 fill:#e1f5fe
    style OP4 fill:#e1f5fe

4.9 tail指针的关键作用

  1. 入队操作:新线程总是通过CAS操作添加到tail指针指向的节点之后
  2. 队列完整性:tail指针确保我们能找到队列的最后一个节点
  3. 并发安全:通过原子更新tail指针,保证多线程入队的线程安全
  4. 遍历优化:某些操作(如hasQueuedThreads)从tail开始反向遍历更高效

5. 总结

通过对ReentrantLock从构造到lock/unlock的完整分析,我们深入理解了:

  1. 继承体系:ReentrantLock → Sync → AQS的层次结构
  2. 模板方法模式:AQS定义框架,具体同步器实现细节
  3. 公平性机制:通过队列检查实现FIFO语义
  4. 重入机制:通过state计数支持同一线程多次获取
  5. 内存模型:volatile + CAS实现线程安全
  6. 阻塞机制:LockSupport提供高效的park/unpark
  7. 队列管理:CLH队列变种实现高效等待

AQS作为Java并发包的基石,其设计思想和实现技巧值得深入学习和借鉴。理解AQS不仅有助于正确使用并发工具,更能提升我们设计高性能并发系统的能力。