深入解析ReentrantLock与AQS源码

Lock底层源码(基于ReentrantLock与AQS)

一、整体架构

Java的Lock接口底层主要由ReentrantLock实现,而ReentrantLock的核心又依赖于AbstractQueuedSynchronizer(AQS)框架。整个机制围绕一个volatile int state变量和CLH变体队列实现。

核心组件关系

ReentrantLock
    └── Sync (抽象内部类)
        ├── NonfairSync (非公平锁实现)
        └── FairSync (公平锁实现)
            └── AbstractQueuedSynchronizer (AQS框架)
                └── Node (队列节点)

二、核心类

1. AbstractQueuedSynchronizer(AQS)抽象队列同步器

位置:java.util.concurrent.locks.AbstractQueuedSynchronizer

核心字段
// 使用volatile修饰,保证多线程间的内存可见性
// 0: 锁空闲  >0: 锁被持有(重入次数)
private volatile int state;

// 记录当前持有独占锁的线程
private transient Thread exclusiveOwnerThread;
CAS修改变量:判断并交换!!!
// this: 当前AQS对象
// STATE: state字段在内存中的偏移量(通过Unsafe获取)
// expect: 旧值
// update: 新值
protected final boolean compareAndSetState(int expect, int update) {
    // 如果当前state==expect,则更新为update并返回true
    // 否则返回false,不进行任何修改
    return U.compareAndSetInt(this, STATE, expect, update);
}
获取/设置锁拥有者
// 设置持有锁的线程(在成功获取锁后调用)
protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

// 获取当前持有锁的线程(用于可重入判断)
protected final Thread getExclusiveOwnerThread() {
    return exclusiveOwnerThread;
}
模板方法 - tryAcquire
// 由子类实现具体的获取锁逻辑
// ReentrantLock.NonfairSync和FairSync会重写此方法
protected boolean tryAcquire(int arg) {
    // AQS不实现,直接抛异常,强制子类重写
    throw new UnsupportedOperationException();
}
模板方法 - tryRelease
// 由子类实现具体的释放锁逻辑
protected boolean tryRelease(int arg) {
    // AQS不实现,直接抛异常,强制子类重写
    throw new UnsupportedOperationException();
}

2. ReentrantLock.Sync - 锁的同步器

位置:java.util.concurrent.locks.ReentrantLock$Sync

abstract static class Sync extends AbstractQueuedSynchronizer {
    
    // 抽象方法:由子类实现初次获取锁的逻辑(非公平/公平策略不同)
    abstract boolean initialTryLock();
    
    // 通用加锁入口方法
    @ReservedStackAccess // 防止栈溢出
    final void lock() {
        // 【关键判断】:调用子类的initialTryLock尝试快速获取锁
        // 如果返回true: 加锁成功,直接返回,不进入队列
        // 如果返回false: 加锁失败,执行AQS的acquire方法进入排队流程
        if (!initialTryLock())
            acquire(1); // 参数1表示每次加锁state+1
    }
}

3. ReentrantLock.NonfairSync - 非公平锁实现

位置:java.util.concurrent.locks.ReentrantLock$NonfairSync

initialTryLock() - 初次尝试获取锁(非公平)
static final class NonfairSync extends Sync {
    
    /**
     * 非公平锁的首次加锁尝试
     * 特点:不管是否有线程在等待,直接CAS抢锁
     */
    final boolean initialTryLock() {
        // 获取当前执行线程的引用
        Thread current = Thread.currentThread();
        
        // 【核心1:CAS抢锁】
        // 无论队列中是否有等待线程,都直接尝试将state从0改为1
        // this: 当前NonfairSync对象
        // 如果成功,说明抢到锁
        if (compareAndSetState(0, 1)) { // first attempt is unguarded (无保护的首次尝试)
            // 设置当前线程为锁的拥有者
            setExclusiveOwnerThread(current);
            // 返回true,lock()方法直接返回,不进入队列
            return true;
        } 
        // 【核心2:可重入逻辑】
        // 如果CAS失败,检查是否是当前线程已持有锁(可重入)
        else if (getExclusiveOwnerThread() == current) { // 可重入情况
            // 获取当前state值并+1(重入次数+1)
            int c = getState() + 1;
            
            // 检查溢出:重入次数不能超过Integer.MAX_VALUE
            if (c < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            
            // 设置新的state值(无需CAS,因为只有持有锁的线程能修改)
            setState(c);
            // 返回true,重入加锁成功
            return true;
        }
        // 【核心3:获取失败】
        // 锁被其他线程持有,返回false,进入AQS排队
        else {
            return false;
        }
    }
tryAcquire() - AQS回调的获取方法
    /**
     * AQS在队列中会调用此方法再次尝试获取锁
     * 注意:此方法不包含可重入逻辑,因为initialTryLock已处理
     */
    protected final boolean tryAcquire(int acquires) {
        // 双重检查:state==0并且CAS成功
        // 这是为了防止在入队前锁刚好被释放,避免不必要的阻塞
        if (getState() == 0 && compareAndSetState(0, acquires)) {
            // 抢占成功,设置拥有者
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        // 否则失败
        return false;
    }
}

4. ReentrantLock.FairSync - 公平锁实现

位置:java.util.concurrent.locks.ReentrantLock$FairSync

initialTryLock() - 初次尝试获取锁(公平)
static final class FairSync extends Sync {
    
    /**
     * 公平锁的首次加锁尝试
     * 特点:检查等待队列,如果有线程在等待,自己就不抢锁
     */
    final boolean initialTryLock() {
        // 获取当前线程
        Thread current = Thread.currentThread();
        // 获取当前state值
        int c = getState();
        
        // 【核心区别1:检查队列】
        if (c == 0) {
            // 【关键】:先判断是否有线程在队列中等待
            // !hasQueuedThreads():如果队列中没有等待线程,才尝试CAS
            // 这是保证公平性的核心:先到先得
            if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } 
        // 【核心2:可重入逻辑】(与非公平锁相同)
        else if (getExclusiveOwnerThread() == current) {
            if (++c < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(c);
            return true;
        }
        // 失败:要么锁被占用,要么队列中有等待线程
        return false;
    }
hasQueuedThreads() - 检查等待队列
    /**
     * 判断同步队列中是否有等待线程
     * 这是公平锁的核心判断
     */
    public final boolean hasQueuedThreads() {
        // 从尾节点向前遍历到head
        // 循环条件:p != h && p != null 表示队列中有多个节点
        for (Node p = tail, h = head; p != h && p != null; p = p.prev) {
            // 检查节点状态
            // status >= 0 表示线程仍在等待
            if (p.status >= 0)
                return true; // 发现有等待线程,返回true
        }
        return false; // 队列为空或只有head节点
    }
}

5. ReentrantLock - 对外的锁接口

位置:java.util.concurrent.locks.ReentrantLock

加锁入口
public class ReentrantLock implements Lock {
    
    // 同步器实例(可以是NonfairSync或FairSync)
    private final Sync sync;
    
    /**
     * 加锁操作
     * 直接调用sync.lock(),策略由子类决定
     */
    public void lock() {
        // 调用Sync子类(NonfairSync或FairSync)的lock方法
        sync.lock(); // 在ReentrantLock.java 第225行附近
    }
解锁入口
    /**
     * 解锁操作
     * 调用AQS的release方法
     */
    public void unlock() {
        // 参数1表示每次解锁state-1
        sync.release(1); 
    }
}

三、加锁流程详细步骤

非公平锁加锁全流程(NonfairSync)

场景:线程A第一次获取锁,线程B尝试获取已被A持有的锁

// 步骤1:调用ReentrantLock.lock()
public void lock() {
    sync.lock(); // 跳转到NonfairSync.lock()
}

// 步骤2:进入Sync.lock()
final void lock() {
    if (!initialTryLock()) // 先尝试快速获取
        acquire(1); // 失败则进入AQS排队
}

// 步骤3:执行NonfairSync.initialTryLock()
final boolean initialTryLock() {
    Thread current = Thread.currentThread();
    
    // 【线程A】:state=0, CAS成功
    if (compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(current); // A成为锁拥有者
        return true; // A加锁成功,直接返回
    }
    // 【线程B】:state=1(CAS失败) 且 拥有者是A(不是B)
    else {
        return false; // B获取失败,进入acquire(1)
    }
}

// 步骤4:线程B进入AQS.acquire()
public final void acquire(int arg) {
    // 再次尝试tryAcquire()(防止在入队前锁刚好释放)
    if (!tryAcquire(arg))
        acquire(null, arg, false, false, false, 0L); // 加入等待队列
}

// 步骤5:执行NonfairSync.tryAcquire()
protected final boolean tryAcquire(int acquires) {
    // B检查时state=1(还是A持有),CAS失败
    // 返回false,进入acquire(...)入队阻塞
    if (getState() == 0 && compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}

// 步骤6:线程B进入CLH队列,调用LockSupport.park()阻塞
// 步骤7:线程A执行unlock(),state变为0,调用LockSupport.unpark(B)
// 步骤8:线程B被唤醒,再次自旋调用tryAcquire(),此时state=0,CAS成功,获取锁

公平锁加锁全流程(FairSync)

与非公平锁的核心区别在第3步:

// 步骤3:执行FairSync.initialTryLock()
final boolean initialTryLock() {
    Thread current = Thread.currentThread();
    int c = getState();
    
    if (c == 0) {
        // 【核心区别】:检查hasQueuedThreads()
        // 即使state=0,如果有线程在等待,也不抢锁
        if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // ...可重入逻辑相同
    return false;
}

// 步骤3.1:hasQueuedThreads()检查
public final boolean hasQueuedThreads() {
    // 如果tail!=head且tail不为null,说明队列中有等待节点
    for (Node p = tail, h = head; p != h && p != null; p = p.prev) {
        if (p.status >= 0) // 发现有效等待节点
            return true; // 返回true,放弃抢锁机会
    }
    return false; // 队列为空,可以公平竞争
}

四、解锁流程详细步骤

解锁全流程(以线程A释放锁为例)

// 步骤1:调用ReentrantLock.unlock()
public void unlock() {
    sync.release(1); // 参数1表示releases=1
}

// 步骤2:进入AQS.release()
public final boolean release(int arg) {
    // 尝试释放锁
    if (tryRelease(arg)) {
        // 释放成功,唤醒后继节点
        signalNext(head);
        return true;
    }
    return false; // 重入锁未完全释放
}

// 步骤3:执行Sync.tryRelease()(ReentrantLock重写的方法)
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
    // 获取当前state并减去releases(releases=1)
    int c = getState() - releases; // c = 当前重入次数 - 1
    
    // 【安全性检查】:只有持有锁的线程才能解锁
    if (getExclusiveOwnerThread() != Thread.currentThread())
        throw new IllegalMonitorStateException(); // 非法状态异常
    
    boolean free = (c == 0); // 判断锁是否完全释放
    
    // 如果state变为0,说明锁已完全释放
    if (free)
        setExclusiveOwnerThread(null); // 清空锁拥有者
    
    // 更新state值(无需CAS,因为当前线程持有锁)
    setState(c); // state减1
    
    return free; // 返回true表示完全释放,需要唤醒等待线程
}

// 步骤4:执行signalNext(head)唤醒后继节点
// 找到head.next对应的线程,调用LockSupport.unpark(thread)

五、关键机制深度解析

1. 可重入性(Reentrancy)实现

// 原理:记录锁持有线程 + state计数器
// 第一次获取锁:state=1, exclusiveOwnerThread=当前线程
// 同一线程再次获取:state=2,3,4...(不重设exclusiveOwnerThread)
// 解锁:state递减,直到state=0才完全释放

2. volatile与CAS无锁化

// volatile:保证所有线程看到的state值是最新的
// CAS操作:compareAndSetState()利用CPU指令实现原子性,无需synchronized
// 性能:在高并发下比重量级锁高效

3. 公平性(Fairness)机制

// 非公平锁:线程直接CAS抢锁,不管队列
// 优点:吞吐量高,线程切换少
// 缺点:可能导致线程饥饿(可以使用公平锁,也就是添加个true)

// 公平锁:先检查hasQueuedThreads(),有等待者就排队
// 优点:保证先到先服务,避免饥饿
// 缺点:吞吐量略低,所有线程必须排队

4. AQS队列机制(CLH变体)

abstract static class Node {
    volatile Node prev;       // 前驱节点
    volatile Node next;       // 后继节点
    Thread waiter;            // 等待的线程
    volatile int status;      // 节点状态:0(初始)
}

// head: 持有锁的节点或虚拟节点
// tail: 队列尾部,新节点从这里加入
// 入队:CAS设置tail,然后prev指向旧tail
// 出队:head向后移动,唤醒next节点

六、总结对比表

特性NonfairSync (非公平锁)FairSync (公平锁)
初次尝试直接CAS抢锁检查队列,无等待者才CAS
吞吐量略低
线程饥饿可能不会
state操作CAS + 直接setCAS + 直接set
适用场景高并发,竞争激烈要求公平性,避免饥饿

七、关键源码行号速查

(行号不一定准,还是自己实操一次最好!)

  • ReentrantLock.lock(): 225行
  • ReentrantLock.unlock(): 173行
  • NonfairSync.initialTryLock(): 229行(state+1)
  • FairSync.initialTryLock(): 263行(state+1)
  • Sync.tryRelease(): 173行(state-1)

八、核心acquire方法详解

源码详解


public final void acquire(int arg) {
    // 这是AQS的入口方法,先快速尝试一次tryAcquire,失败才进入复杂的acquire排队逻辑
    // arg=1表示获取1个资源(对于ReentrantLock就是锁的重入次数+1)
    if (!tryAcquire(arg))
        // acquire是更底层的实现,会处理入队、自旋、阻塞等完整生命周期
        // 参数含义:node=null(还未创建节点), arg=1, shared=false(独占模式), interruptible=false(不可中断), timed=false(不超时), time=0L
        acquire(null, arg, false, false, false, 0L);
}

/**
 * AQS最核心、最复杂的获取锁方法,融合了自旋、排队、阻塞、超时、中断等全部逻辑
 * 这是现代JVM并发编程的巅峰之作,性能优化的集大成者
 * 
 * @param node 当前线程对应的队列节点(可能为null,表示还没创建)
 * @param arg 获取锁的资源数(ReentrantLock传1)
 * @param shared 是否为共享模式(读写锁读锁为true,ReentrantLock为false)
 * @param interruptible 是否可被中断唤醒(true表示lockInterruptibly)
 * @param timed 是否有超时时间(true表示tryLock带超时)
 * @param time 超时截止时间(纳秒)
 * @return 1表示成功获取锁,负值表示失败(被中断或超时)
 */
final int acquire(Node node, int arg, boolean shared,
                      boolean interruptible, boolean timed, long time) {
    // 获取当前线程引用,后续所有操作都针对这个线程
    Thread current = Thread.currentThread();
    
    // spins: 当前自旋次数(队首线程在park前的自旋)
    // postSpins: 被唤醒后的再次自旋次数(指数退避策略)
    // 使用byte类型是为了节省内存空间
    byte spins = 0, postSpins = 0;
    
    // interrupted: 是否被中断过的标记
    // first: 当前节点是否为队列第一个等待节点(动态变化)
    boolean interrupted = false, first = false;
    
    // pred: 前驱节点缓存,避免重复读取(性能优化)
    Node pred = null;

    /*
     * 这是整个方法的核心循环,线程会在这个循环中经历以下状态转换:
     * 未入队 -> 尝试获取 -> 创建节点 -> 入队 -> 自旋 -> 阻塞 -> 被唤醒 -> 再次尝试
     * 
     * 设计理念:延迟一切(lazy evaluation)+ 乐观尝试(optimistic retry)
     * 不到万不得已不阻塞,阻塞前也要自旋几次,平衡CPU消耗和响应延迟
     */

    for (;;) { // 无限循环,直到获取锁或取消
        // ------------------- 阶段1:判断当前节点是否为队列第一个等待节点 -------------------
        // 这个判断极其关键,只有队首节点才有资格尝试获取锁(公平性保证)
        if (!first && (pred = (node == null) ? null : node.prev) != null &&
            !(first = (head == pred))) {
            
            // 进入这里说明:1. 不是队首 2. 节点已入队 3. 前驱不是head
            
            // 情况1:前驱节点状态为CANCELLED(>0表示取消),需要清理无效节点
            // 否则会导致队列死链,永远无法唤醒
            if (pred.status < 0) {
                cleanQueue();           // 清理前驱已取消的节点,重建队列链表
                continue;               // 清理后重新循环
            } 
            // 情况2:前驱的prev为null,说明前驱正在入队中,链接未完成
            // 此时需要自旋等待,避免过早park错过唤醒信号
            else if (pred.prev == null) {
                Thread.onSpinWait();    // 调用CPU的PAUSE指令,降低功耗,提高自旋效率
                continue;               // 继续循环等待前驱入队完成
            }
        }
        
        // ------------------- 阶段2:尝试获取锁(黄金时机) -------------------
        // 只有两种情况可以在此尝试获取锁:
        // 1. first=true: 已经是队首节点,轮到我获取锁了
        // 2. pred=null: 还没入队,新来的直接尝试(非公平性的体现)
        if (first || pred == null) {
            boolean acquired;
            try {
                // 调用子类实现的tryAcquire或tryAcquireShared
                // ReentrantLock会调用NonfairSync/FairSync的tryAcquire进行CAS抢锁
                if (shared)
                    acquired = (tryAcquireShared(arg) >= 0);
                else
                    acquired = tryAcquire(arg);
            } catch (Throwable ex) {
                // 获取锁过程中抛出异常(罕见),取消当前节点并重新抛出
                cancelAcquire(node, interrupted, false);
                throw ex;
            }
            
            // 成功获取锁!需要进行一系列状态更新和清理工作
            if (acquired) {
                if (first) {
                    // 当前节点晋升为新的head(旧head已释放锁)
                    node.prev = null;     // 断开与前驱的链接
                    head = node;          // 设置新head
                    pred.next = null;     // 帮助GC回收旧head
                    node.waiter = null;   // 清空线程引用,节点已获取锁不再需要waiter
                    
                    // 共享模式下需要传播唤醒(如读写锁读锁)
                    if (shared)
                        signalNextIfShared(node);
                    
                    // 如果被中断过且需要响应中断,恢复中断标记
                    if (interrupted)
                        current.interrupt();
                }
                return 1; // **成功获取锁,返回正值1,方法结束**
            }
            // 获取失败,继续循环(锁还没释放)
        }
        
        // ------------------- 阶段3:创建节点并入队(第一次循环时node为null) -------------------
        
        // 情况1:节点还未创建,延迟创建以避免不必要的对象分配(性能优化)
        if (node == null) {
            if (shared)
                node = new SharedNode();    // 创建共享模式节点
            else
                node = new ExclusiveNode(); // 创建独占模式节点(ReentrantLock走这里)
        } 
        // 情况2:节点已创建,但还没有入队(prev为null)
        else if (pred == null) {
            node.waiter = current;          // 将当前线程绑定到节点
            
            Node t = tail;                  // 获取当前tail(队尾)
            node.setPrevRelaxed(t);         // 先用relaxed模式设置前驱,避免内存屏障开销
            
            // 如果tail为null,说明队列未初始化,先创建虚拟head节点
            if (t == null)
                tryInitializeHead();        // 初始化head和tail
            // CAS竞争失败:有其他线程同时入队,回滚prev,下次循环重试
            else if (!casTail(t, node))
                node.setPrevRelaxed(null);  // 失败必须回滚,否则会成为野指针
            // CAS成功:成为新的tail,完成双向链表链接
            else
                t.next = node;              // 旧tail的next指向新节点,完成入队
        }
        
        // ------------------- 阶段4:自适应自旋(Park前的最后挣扎) -------------------
        // 已经成为队首,但锁还没释放,先自旋几次再park
        // 这样可以减少线程上下文切换的开销,提高吞吐量
        else if (first && spins != 0) {
            --spins;                        // 自旋次数递减
            Thread.onSpinWait();            // CPU友好的自旋等待(PAUSE指令)
        }
        
        // ------------------- 阶段5:准备阻塞(设置状态并Park) -------------------
        
        // 情况1:节点状态为0(初始状态),先设置为WAITING(可被唤醒状态)
        // 这是两步阻塞法:先标记状态,下次循环才真正park
        // 避免在设置状态前被唤醒导致信号丢失(race condition)
        else if (node.status == 0) {
            node.status = WAITING;          // 设置状态为等待,使能被唤醒
        } 
        
        // 情况2:状态已设置,真正阻塞当前线程
        else {
            // 指数退避策略:每次被唤醒后,postSpins自旋次数翻倍
            // 应对虚假唤醒,避免频繁park/unpark
            spins = postSpins = (byte)((postSpins << 1) | 1);
            
            long nanos;
            
            // 根据是否超时选择不同的park方式
            if (!timed)
                LockSupport.park(this);     // 无限期阻塞,等待unpark
            else if ((nanos = time - System.nanoTime()) > 0L)
                LockSupport.parkNanos(this, nanos); // 超时阻塞
            else
                break;                      // 超时时间已到,退出循环
            
            // 被唤醒后清除WAITING状态,准备下次循环重新尝试
            node.clearStatus();
            
            // 检查是否被中断,如果是可中断模式则退出循环
            // Thread.interrupted()会清除中断标记,所以用|=记录历史上是否中断过
            if ((interrupted |= Thread.interrupted()) && interruptible)
                break;                      // 被中断且允许中断,退出循环
        }
    }
    
    // ------------------- 阶段6:取消获取(超时或被中断) -------------------
    // 只有超时或被中断才会走到这里
    // cancelAcquire会清理节点,还原状态,返回负值表示失败
    return cancelAcquire(node, interrupted, interruptible);
}

完整流程图解(白话版)

线程来了 → 先抢锁(tryAcquire)→ 抢不到 → 进acquire方法
   ↓
循环开始:我是队首吗?→ 不是 → 前驱正常吗?→ 不正常 → 清理队列
   ↓
是队首/还没入队 → 再抢一次锁 → 抢到 → 晋升head → 返回成功
   ↓
没抢到 → 我创建节点了吗?→ 没 → 创建节点
   ↓
节点有了 → 我入队了吗?→ 没 → CAS入队 → 失败就重试
   ↓
入队了 → 我是队首吗?→ 是 → 自旋几次(别急着睡)
   ↓
自旋够了 → 设置WAITING标记 → 调用park()阻塞(真正睡觉)
   ↓
被唤醒 → 清除标记 → 检查中断/超时 → 回到循环开始再抢锁
   ↓
超时/中断 → cancelAcquire → 返回失败

核心设计

  1. 延迟一切(Lazy Evaluation):节点延迟创建、阻塞延迟执行、清理延迟进行,避免不必要的开销
  2. 乐观尝试(Optimistic Retry):不到最后一刻不放弃,park前多次tryAcquire,减少线程切换
  3. 两步阻塞(Two-Phase Park):先标记状态再真正park,避免信号丢失(经典race condition解决方案)
  4. 指数退避(Exponential Backoff):被唤醒后自旋次数指数增长,应对虚假唤醒
  5. 自适应(Adaptive):根据first/pred动态调整策略,不是队首就安心等待,是队首就积极尝试
  6. 无锁化(Lock-Free):队列操作全程CAS,只有park是内核调用,最大化并发性能
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值