• ReentrantLock 源码


    ReentrantLock 的 lock 方法源码

    ReentrantLock 中的 lock 方法,主要调用内部类 Sync 中的抽象 lock 方法。该方法主要有两套实现,一套是公平锁,一套是非公平锁。

    公平锁

    1. final void lock() {
    2. acquire(1);
    3. }

    公平锁中,直接调用 acquire 方法。

    非公平锁

    1. final void lock() {
    2. if (compareAndSetState(0, 1))
    3. setExclusiveOwnerThread(Thread.currentThread());
    4. else
    5. acquire(1);
    6. }

    非公平锁中,首先尝试将 AQS 中的 state 属性从 0 变成 1,如果成功,则代表获取锁资源成功;否则调用 acquire 方法。

    ReentrantLock 的 acquire 方法源码

    acquire 方法中没有实际的业务处理,都是在调用其他方法。

    1. public final void acquire(int arg) {
    2. if (!tryAcquire(arg) &&
    3. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    4. selfInterrupt();
    5. }

    首先调用 tryAcquire 方法,尝试获取锁资源,如果获取成功,则返回 true,方法结束。如果获取失败,则调用 && 后面的方法。
    调用 addWaiter() 方法,将线程封装到 Node 节点并添加到队列尾部。
    之后再调用 acquireQueued() 方法查看当前排队的 Node 是否在队列的前面,如果在前面,尝试获取锁资源。如果没在前面,线程进入到阻塞状态。

    ReentrantLock 的 tryAcquire 方法源码

    tryAcquire() 方法分公平锁和非公平锁两套实现,主要做了两件事:

    如果 AQS 当前 state 为 0,尝试获取锁资源。
    如果 AQS 当前 state 不为 0,查看是否是可重入操作。

    公平锁

    1. protected final boolean tryAcquire(int acquires) {
    2. // 获取当前线程
    3. final Thread current = Thread.currentThread();
    4. // 获取AQS当前state值
    5. int c = getState();
    6. // 判断state是否为0,为0则代表当前没有线程持有锁
    7. if (c == 0) {
    8. // 首先判断是否有线程在排队,如果有,tryAcquie()方法直接返回false
    9. // 如果没有,则尝试获取锁资源
    10. if (!hasQueuedPredecessors() &&
    11. compareAndSetState(0, acquires)) {
    12. setExclusiveOwnerThread(current);
    13. return true;
    14. }
    15. }
    16. // 如果state != 0,则代表有线程持有锁资源
    17. // 判断占有锁的线程是不是当前线程,如果是,则进行可重入操作
    18. else if (current == getExclusiveOwnerThread()) {
    19. // 可重入
    20. int nextc = c + acquires;
    21. // 检查锁重入是否超过最大值,二进制第一位表示符号
    22. // 01111111 11111111 11111111 11111111
    23. if (nextc < 0)
    24. throw new Error("Maximum lock count exceeded");
    25. setState(nextc);
    26. return true;
    27. }
    28. return false;
    29. }

    非公平锁

    1. final boolean nonfairTryAcquire(int acquires) {
    2. // 获取当前线程
    3. final Thread current = Thread.currentThread();
    4. // 获取AQS当前state值
    5. int c = getState();
    6. // 如果state == 0,说明没有线程占用着当前的锁资源
    7. if (c == 0) {
    8. // CAS直接尝试获取锁资源,直接抢锁,不管有没有线程在队列中
    9. if (compareAndSetState(0, acquires)) {
    10. setExclusiveOwnerThread(current);
    11. return true;
    12. }
    13. }
    14. else if (current == getExclusiveOwnerThread()) {
    15. int nextc = c + acquires;
    16. // 检查锁重入是否超过最大值,二进制第一位表示符号
    17. // 01111111 11111111 11111111 11111111
    18. if (nextc < 0) // overflow
    19. throw new Error("Maximum lock count exceeded");
    20. // 修改state当前值
    21. setState(nextc);
    22. return true;
    23. }
    24. return false;
    25. }

    ReentrantLock 的 addWaiter 方法源码

    1. private Node addWaiter(Node mode) {
    2. // 将当前线程封装为Node对象,modenull,代表互斥锁
    3. Node node = new Node(Thread.currentThread(), mode);
    4. // Try the fast path of enq; backup to full enq on failure
    5. // pred指向tail
    6. Node pred = tail;
    7. if (pred != null) {
    8. // 当前线程Node节点的prev指向pred节点
    9. node.prev = pred;
    10. // 以CAS方式,尝试将node节点变成tail
    11. if (compareAndSetTail(pred, node)) {
    12. // 将pred的next指向node
    13. pred.next = node;
    14. return node;
    15. }
    16. }
    17. // 如果上述方式,CAS操作失败,导致加入到AQS末尾失败,就基于enq的方式添加到AQS队列
    18. enq(node);
    19. return node;
    20. }

    在 tryAcquire() 方法获取锁资源失败之后,首先创建当前线程的 Node 节点,之后将该节点添加到队列尾部。

    1. private Node enq(final Node node) {
    2. // 死循环,直到插入成功
    3. for (;;) {
    4. Node t = tail;
    5. if (t == null) { // Must initialize
    6. //如果尾节点为null,说明同步队列还未初始化,则CAS操作新建头节点
    7. if (compareAndSetHead(new Node()))
    8. tail = head;
    9. } else {
    10. // 将node的prev指向当前的tail节点
    11. node.prev = t;
    12. // CAS尝试将node变成tail节点
    13. if (compareAndSetTail(t, node)) {
    14. // 将之前尾节点的next指向要插入的节点
    15. t.next = node;
    16. return t;
    17. }
    18. }
    19. }
    20. }

    ReentrantLock 的 acquirdQueued 方法源码

    1. final boolean acquireQueued(final Node node, int arg) {
    2. boolean failed = true;
    3. try {
    4. boolean interrupted = false;
    5. for (;;) {
    6. // 获取node的前一个节点
    7. final Node p = node.predecessor();
    8. // 如果前一个节点为head并尝试获取锁资源
    9. if (p == head && tryAcquire(arg)) {
    10. // 尝试获取锁资源成功,将node节点设置为头节点,thread和prev属性置位null
    11. setHead(node);
    12. // 将之前的头节点的next指向null,帮助快速GC
    13. p.next = null; // help GC
    14. failed = false;
    15. return interrupted;
    16. }
    17. // 如果前一个节点不是head或者获取锁资源失败
    18. if (shouldParkAfterFailedAcquire(p, node) &&
    19. parkAndCheckInterrupt())
    20. interrupted = true;
    21. }
    22. } finally {
    23. if (failed)
    24. cancelAcquire(node);
    25. }
    26. }
    1. // 确保上一个节点的状态是正确的
    2. private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    3. int ws = pred.waitStatus;
    4. if (ws == Node.SIGNAL)
    5. return true;
    6. if (ws > 0) {
    7. // 循环往前找,找到一个状态小于等于0的节点
    8. do {
    9. node.prev = pred = pred.prev;
    10. } while (pred.waitStatus > 0);
    11. pred.next = node;
    12. } else {
    13. // 如果不是-1,但是小于等于0,将状态修改为-1
    14. compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    15. }
    16. return false;
    17. }

    acquireQueued 方法会查看当前排队的 Node 的前一个节点是不是 head,如果是,尝试获取锁资源。如果不是或者获取锁资源失败,那么就尝试将当前 Node 的线程挂起。
    在挂起线程前,需要确认当前节点的上一个节点的状态是小于等于 0:
    如果为 1,代表是取消的节点,不能挂起
    如果为 -1,代表挂起当前线程
    如果为 -2,-3,需要将状态改为 -1 之后,才能挂起当前线程

    ReentrantLock 的 unlock 方法源码

    1. public void unlock() {
    2. sync.release(1);
    3. }
    1. public final boolean release(int arg) {
    2. // 核心释放锁方法
    3. if (tryRelease(arg)) {
    4. Node h = head;
    5. // 如果头节点不为null,并且头节点的状态不为0,唤醒排队的线程
    6. if (h != null && h.waitStatus != 0)
    7. // 唤醒线程
    8. unparkSuccessor(h);
    9. return true;
    10. }
    11. return false;
    12. }
    1. private void unparkSuccessor(Node node) {
    2. // 获取头节点状态
    3. int ws = node.waitStatus;
    4. // 如果头节点状态小于0,换为0
    5. if (ws < 0)
    6. compareAndSetWaitStatus(node, ws, 0);
    7. // 拿到当前节点的next
    8. Node s = node.next;
    9. // 如果s == null ,或者s的状态为1
    10. if (s == null || s.waitStatus > 0) {
    11. // next节点不需要唤醒,需要唤醒nextnext
    12. s = null;
    13. // 从尾部往前找,找到状态正常的节点。(小于等于0代表正常状态)
    14. for (Node t = tail; t != null && t != node; t = t.prev)
    15. if (t.waitStatus <= 0)
    16. s = t;
    17. }
    18. // 经过循环的获取,如果拿到状态正常的节点,并且不为null
    19. if (s != null)
    20. // 线程唤醒
    21. LockSupport.unpark(s.thread);
    22. }
    1. // 核心的释放锁资源方法
    2. protected final boolean tryRelease(int releases) {
    3. // state - 1
    4. int c = getState() - releases;
    5. // 如果释放锁的线程不是占用锁的线程,抛异常
    6. if (Thread.currentThread() != getExclusiveOwnerThread())
    7. throw new IllegalMonitorStateException();
    8. // 是否成功的将锁资源释放
    9. boolean free = false;
    10. if (c == 0) {
    11. // 如果state = 0,代表成功释放锁资源
    12. free = true;
    13. setExclusiveOwnerThread(null);
    14. }
    15. // 设置state值
    16. setState(c);
    17. return free;
    18. }
  • 相关阅读:
    Linux基础概念,目录文件操作命令,压缩命令:
    弃繁就简!一行代码搞定 Python 日志!
    git在merge时做了些什么
    普通话水平测试考试命题自拟-(背诵版)
    【Ubuntu18.04】Autoware.ai安装
    普洛斯数据中心发布DC Brain系统,科技赋能智慧化运营管理
    nacos注册中心AP核心源码
    聊天室案例实现保姆级教学
    mysql导入CSV乱码问题解决
    Java 线程创建与常用方法
  • 原文地址:https://blog.csdn.net/LBWNB_Java/article/details/127047670