ReentrantLock与AbstractQueuedSynchronizer源码解析,一文读懂底层原理

AQS被称为java并发编程的基石,AQS使用一个用volatile修饰的int型的state变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。通过 CLH 队列状态变量 的协作,将复杂的线程调度抽象为简单使用CAS对的 state 操作。

ReentranLock:可重入锁。可以公平获取锁和非公平获取锁。

AQS源码

AQS的整体结构

java 复制代码
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

    protected AbstractQueuedSynchronizer() { }
    
    // 等待队列头部
    private transient volatile Node head;

    // 等待队列的尾部。初始化后,仅通过 casTail 修改。
    private transient volatile Node tail;

    // 同步状态
    private volatile int state;
    
    static final int WAITING   = 1;          // must be 1
    static final int CANCELLED = 0x80000000; // must be negative
    static final int COND      = 2;          // in a condition wait

    /** CLH Nodes */
    abstract static class Node {
        volatile Node prev;       // 头节点
        volatile Node next;       // 尾节点
        Thread waiter;            // 线程
        volatile int status;      // 状态
        //...
    }

    // Concrete classes tagged by type
    static final class ExclusiveNode extends Node { }
    static final class SharedNode extends Node { }

    static final class ConditionNode extends Node
        implements ForkJoinPool.ManagedBlocker {
        // ...
    }
    
    public class ConditionObject implements Condition, java.io.Serializable {
        private transient ConditionNode firstWaiter;
        private transient ConditionNode lastWaiter;
        public ConditionObject() {
        }
    }
 }

入队操作

java 复制代码
final void enqueue(Node node) {
    if (node != null) {     // 确保节点非空
        for (; ; ) {    // 自旋直到成功
            Node t = tail;  // 获取当前尾节点
            node.setPrevRelaxed(t);        // 临时设置节点的前驱为当前尾节点
            if (t == null)                 // 如果尾节点为null(队列为空),则尝试初始化头节点
                tryInitializeHead();
            else if (casTail(t, node)) {    // CAS操作:尝试将尾节点替换为新节点
                t.next = node;      // CAS成功后,将原尾节点的next指向新节点
                if (t.status < 0)          // 如果原尾节点的状态小于0
                    LockSupport.unpark(node.waiter);    // 唤醒新节点关联的线程
                break;
            }
        }
    }
}

 // 如果在从尾部遍历中找到节点,则返回真
final boolean isEnqueued(Node node) {
    for (Node t = tail; t != null; t = t.prev)
        if (t == node)
            return true;
    return false;
}

唤醒后继节点

java 复制代码
private static void signalNext(Node h) {
    Node s;
    if (h != null && (s = h.next) != null && s.status != 0) {
        s.getAndUnsetStatus(WAITING);    // 取消等待WAITING状态
        LockSupport.unpark(s.waiter);    // 唤醒线程
    }
}

//
如果处于共享模式,则唤醒给定的节点
private static void signalNextIfShared(Node h) {
    Node s;
    if (h != null && (s = h.next) != null &&
            (s instanceof SharedNode) && s.status != 0) {
        s.getAndUnsetStatus(WAITING);
        LockSupport.unpark(s.waiter);
    }
}

清理队列和取消获取

java 复制代码
private void cleanQueue() {
    for (; ; ) {                              
        for (Node q = tail, s = null, p, n; ; ) { // 三元组遍历:当前(q),前驱(p),后继(s)
            if (q == null || (p = q.prev) == null)
                return;                      // 队列为空或到达头部,终止清理
            // 快照一致性检查,若后继节点为空且当前不是尾节点或后继不为空,后继节点的
            // 前驱节点不是当前节点q或后继节点已经被取消,说明当前链已经被改变
            if (s == null ? tail != q : (s.prev != q || s.status < 0))
                break;                       // // 快照过期,中断当前遍历
            // 发现已取消节点
            if (q.status < 0) {
                if ((s == null ? casTail(q, p) : s.casPrev(q, p)) &&
                        q.prev == p) {
                    p.casNext(q, s);         // 更新前驱的后向链接
                    if (p.prev == null)     // 如果前驱成为新头节点
                        signalNext(p);      // 唤醒后继节点
                }
                break;       // 本次清理完成,跳出内层循环
            }
            // 链接修复机制
            if ((n = p.next) != q) {        // 检测到断链
                if (n != null && q.prev == p) {
                    p.casNext(n, q);    // 修复前驱的后向链接
                    if (p.prev == null)     // 如果前驱是头节点
                        signalNext(p);      // 唤醒后继
                }
                break;      // 修复完成,重新开始
            }
            // 前移指针
            s = q;   // 后继指针前移
            q = q.prev; // 当前指针前移
        }
    }
}

// 取消正在进行的尝试获取锁的节点。
private int cancelAcquire(Node node, boolean interrupted,
                          boolean interruptible) {
    if (node != null) {
        node.waiter = null;      // 解除对线程的引用,帮助GC
        node.status = CANCELLED;        // 标记节点为取消状态
        // 如果节点已链接到队列中
        if (node.prev != null)
            cleanQueue();       // 触发队列清理机制
    }
    // 2. 中断状态处理
    if (interrupted) {
        if (interruptible)  // 允许中断:直接返回取消状态
            return CANCELLED;
        else
            Thread.currentThread().interrupt();     // 不允许中断:恢复线程中断状态
    }
    return 0;
}

ReentranLock源码

我们先来看一下ReentranLock的整体结构和构造函数

整体结构:

java 复制代码
public class ReentrantLock implements Lock, java.io.Serializable {
    
    private final Sync sync;
    
    abstract static class Sync extends AbstractQueuedSynchronizer {

    }

    /**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync {
        
    }

    /**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        
    }
 }

构造函数:

java 复制代码
public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

从上面我们看到,ReentranLock里面使用了继承了AQS的同步器。实现了公平构造器和非公平同步器。默认使用非公平同步器,使用时构造方法是传入的fair参数值为true使用公平同步器。

ReentranLock的加锁过程。

java 复制代码
    public void lock() {
        // 调用同步器的lock()方法
        sync.lock();
    }
    
    abstract static class Sync extends AbstractQueuedSynchronizer {
        final void lock() {
            if (!initialTryLock())
                acquire(1);
             }
    }

到目前为止NonfairSyncfairSync过程都是相同的。接下来就是两者的不同之处了

java 复制代码
// fairSync的initialTryLock()方法
final boolean initialTryLock() {
    // 获取当前线程
    Thread current = Thread.currentThread();
    // 获取当前同步状态
    int c = getState();
    // 如果当前没有线程获取锁
    if (c == 0) {
        // 查询是否有任何线程正在排队等待acquire
        // CAS将state的值设为1,表示获取到锁
        // 将获取到锁的线程设为当前线程
        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;
}

// NonfairSync的initialTryLock()方法
final boolean initialTryLock() {
    // 获取当前线程
    Thread current = Thread.currentThread();
    // CAS将state值设为1
    if (compareAndSetState(0, 1)) { 
        将获取到锁的线程设为当前线程
        setExclusiveOwnerThread(current);
        return true;
    } else if (getExclusiveOwnerThread() == current) {   // 如果当前线程之前已经获取到锁
        // 增加重入次数
        int c = getState() + 1;
        // 判断重入次数是否达到最大值
        if (c < 0) 
            throw new Error("Maximum lock count exceeded");
        setState(c);
        return true;
    } else
        return false;
}

根据源码我们可以看到,公平获取锁和非公平获取的区别就是:

公平获取锁还需要判断AQS的CLH队列是否有线程正在等待获取锁,有则无法获取到锁,加入队列

非公平获取锁只要能够将state的值从0修改为1成功就可以获取到锁

注:非公平锁指的是当前线程A刚执行完,这时新来一个线程B要获取锁,那么这个线程B就极可能更快地获取锁。非公平指的是未入队的线程相比已经入队的线程更先获取到锁。一旦线程入队,那么都是按照先来先服务进行获取锁,入对的线程都是公平的。并不是当前锁空闲时随机从同步队列中获取一个线程进行加锁。

非公平锁相比于公平锁效率更高,有以下几个原因:

  1. 从获取锁的过程来看,公平锁还需要判断同步队列是否有线程在等待,而非公平锁无需判断
  2. 公平锁还需要等待队列中的线程被唤醒然后获取锁;非公平无需等待,有活跃的线程到来直接获取锁
  3. 公共锁获取锁时还要检查是否是第一个节点线程,不是要需要切换

非公平获取锁可能存在线程饿死的情况,不断有新来的未入队的线程获取锁,会造成在队列中的线程一直无法获取到锁的情况。

initialTryLock()可以理解为第一次尝试获取锁,即使未获取到锁也不会进行入队操作。入队操作还在后面

initialTryLock()中没有获取到会进入AQS的acquire()方法,

java 复制代码
public final void acquire(int arg) {
    if (!tryAcquire(arg))
        acquire(null, arg, false, false, false, 0L);
}

AQS的tryAcquire(int arg)方法如下,被ReentranLock方法的同步器进行重写,实际调用的是ReentranLock中同步器重写的方法

java 复制代码
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

tryAcquire(int arg)重写方法如下

java 复制代码
// fairSync的此方法
protected final boolean tryAcquire(int acquires) {
    // 1、锁未被占用
    // 2、当前线程是队列首节点
    // 3、CAS更新锁状态
    if (getState() == 0 && !hasQueuedPredecessors() &&
        compareAndSetState(0, acquires)) {
        // 将当前线程设置为持有锁的线程
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}

// NonfairSync的此方法
protected final boolean tryAcquire(int acquires) {
    // 锁未被占用,CAS设置当前状态
    if (getState() == 0 && compareAndSetState(0, acquires)) {
        // 将当前线程设置为持有锁的线程
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}

如果此时还是没有获取到锁,就会进入AQSacquire()方法进行再次尝试和入队。

java 复制代码
final int acquire(Node node, int arg, boolean shared,
                  boolean interruptible, boolean timed, long time) {
    Thread current = Thread.currentThread();
    byte spins = 0, postSpins = 0;
    boolean interrupted = false, first = false;
    Node pred = null;               

    for (;;) {
        // node不是首节点,前驱节点不为null,前驱节点不是head
        if (!first && (pred = (node == null) ? null : node.prev) != null &&
            !(first = (head == pred))) {
            // 判断前驱节点状态是否处于cancel状态
            if (pred.status < 0) {
                // 清理队列
                cleanQueue();
                continue;
            } else if (pred.prev == null) {   // 前驱节点为null,自旋
                Thread.onSpinWait();
                continue;
            }
        }
        if (first || pred == null) {
            boolean acquired;
            // 此时还未入队,再次尝试获取锁,若获取到了锁直接返回
            try {
                // 是否共享获取锁
                if (shared)
                    acquired = (tryAcquireShared(arg) >= 0);
                else
                    acquired = tryAcquire(arg);
            } catch (Throwable ex) {
                // 取消获取锁
                cancelAcquire(node, interrupted, false);
                throw ex;
            }
            // 获取成功,释放节点
            if (acquired) {
                if (first) {   // 若为true,则此时肯定已入队
                    node.prev = null;
                    head = node;
                    pred.next = null;
                    node.waiter = null;
                    // 如果是共享获取锁,唤醒后续节点
                    if (shared)
                        signalNextIfShared(node);
                    if (interrupted)
                        current.interrupt();
                }
                return 1;
            }
        }
        // 开始入队
        Node t;
        // 如果尾节点为null,进行初始化队列
        if ((t = tail) == null) {           
            if (tryInitializeHead() == null)  // 若初始化失败,则直接间隔尝试获取锁
                return acquireOnOOME(shared, arg);  // jdk17并没有这一步,后面才加入的,初始化失败会一直尝试初始化
        } else if (node == null) {          // 初始化此线程对应的节点
            try {
                node = (shared) ? new SharedNode() : new ExclusiveNode();
            } catch (OutOfMemoryError oome) {
                return acquireOnOOME(shared, arg);
            }
        } else if (pred == null) {          // 尝试入队
            node.waiter = current;          // 将该节点的所对应的线程设为当前线程
            node.setPrevRelaxed(t);         // 将尾节点设为当前节点的前驱节点
            if (!casTail(t, node))          // 将此节点设为尾节点
                // 若设置失败,将前驱节点置为null。
                node.setPrevRelaxed(null);  // 设置失败说明可能有别的线程设置自身为尾节点,此节点要入队位置的前驱节点不再是之前的尾节点了
            else
                t.next = node;
        } else if (first && spins != 0) {
            --spins;                        // 减少自旋次数
            Thread.onSpinWait();
        } else if (node.status == 0) {
            node.status = WAITING;          // 自旋次数用尽后更新状态
        } else {
            long nanos;
            spins = postSpins = (byte)((postSpins << 1) | 1);  // 重至自旋次数
            if (!timed)    // 非超时模式
                LockSupport.park(this);   // 挂起线程,无限期阻塞
            else if ((nanos = time - System.nanoTime()) > 0L)   // 计算剩余时间
                LockSupport.parkNanos(this, nanos);  //限时阻塞
            else  // 已超时,跳出循环
                break;
            // 唤醒后状态管理,清除等待状态标记
            node.clearStatus();
            // 中断响应
            if ((interrupted |= Thread.interrupted()) && interruptible)
                break;   // 响应中断退出
        }
    }
    return cancelAcquire(node, interrupted, interruptible);
}

根据前面的源码我们知道传入的参数为acquire(null, arg, false, false, false, 0L);

该方法的执行过程如下:

  1. 再次尝试获取锁,此后每次循环只要还没有入队都会尝试获取锁
  2. 获取锁失败,判断是否需要初始化队列
  3. 创建节点
  4. 设置前驱节点,入队
  5. 入队之后查看自身是否是头节点,不是则一直自旋
  6. 成为首节点之后尝试获取锁,获取不到更新status状态
  7. 再次重试,还是获取不到锁进行无限期挂起(对于设置了超时时间的,未超时一直重试,直到超时,取消获取锁)

以上就是ReentrantLock的加锁全过程了。

ReentrantLock解锁

公平同步器和非公平同步器释放锁的过程是一样的

java 复制代码
public void unlock() {
    sync.release(1);
}

调用Sync同步器的release()解锁

java 复制代码
public final boolean release(int arg) {
    // 尝试释放资源
    if (tryRelease(arg)) {
        // 唤醒队列的下一个节点
        signalNext(head);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
     // 获取释放后的状态
    int c = getState() - releases;
    // 检查持有锁的线程是否是当前线程
    if (getExclusiveOwnerThread() != Thread.currentThread())
        throw new IllegalMonitorStateException();
    // 若释放之后同步状态为0
    boolean free = (c == 0);
    if (free)
        setExclusiveOwnerThread(null);   // 将记录当前持有锁的线程字段置null
    setState(c);   // 更新同步状态
    return free;
}

以上就是ReentrantLock与AbstractQueuedSynchronizer加锁过程的核心源码解析。

相关推荐
翻滚吧键盘1 小时前
Spring Boot,两种配置文件
java·spring boot·后端
GoGeekBaird7 小时前
69天探索操作系统-第66天:为现代操作系统设计高级实时进程间通信机制
后端·操作系统
还是鼠鼠7 小时前
单元测试-概述&入门
java·开发语言·后端·单元测试·log4j·maven
我最厉害。,。9 小时前
接口安全&SOAP&OpenAPI&RESTful&分类特征导入&项目联动检测
后端·restful
AntBlack11 小时前
计算机视觉 : 端午无事 ,图像处理入门案例一文速通
后端·python·计算机视觉
福大大架构师每日一题12 小时前
2025-06-02:最小可整除数位乘积Ⅱ。用go语言,给定一个表示正整数的字符串 num 和一个整数 t。 定义:如果一个整数的每一位都不是 0,则称该整数为
后端
Code_Artist12 小时前
[Mybatis] 因 0 != null and 0 != '' 酿成的事故,害得我又过点啦!
java·后端·mybatis
程序员博博12 小时前
看到这种代码,我直接气到想打人
后端
南雨北斗12 小时前
php 图片压缩函数
后端
L2ncE12 小时前
ES101系列08 | 数据建模和索引重建
java·后端·elasticsearch