ReentrantLock源码详解

本文详细解析了ReentrantLock的加锁和解锁逻辑,涉及AQS框架、CAS操作、线程等待队列和重入锁的概念,为学习者提供复习和参考资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言

ReentrantLock源码看了很多遍,也总是忘,这次把源码逻辑仔仔细细的写一遍,为了供以后复习,也给各位正在学习的同学们一个参考。

二、ReentrantLock简介

相信大家都熟悉,ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。

三、加锁逻辑

假设有3个线程1,2,3依次去获取锁。

ReentrantLock#lock

        final void lock() {
        // 使用CAS修改State的值,从0改成1,修改成功则加锁成功。
            if (compareAndSetState(0, 1))
            // 加锁成功把当前线程设置为独占线程
               setExclusiveOwnerThread(Thread.currentThread());
            else
            // 再次获取锁,还是获取失败添加到等待队列
                acquire(1);
        }

AQS#acquire

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

AQS#tryAcquire:此方法会先获取state的值,如果是0,再CAS尝试获取一下锁,获取成功就返回了,代表加锁成功。同时也会判断一下是否是冲入锁,如果当前线程和getExclusiveOwnerThread是同一个,说明是重入锁,State+1,同时代表加锁成功。否则代表加锁失败。

AQS#addWaiter:如果加锁失败,会进入该方法,主要是入双向队等待操作。

AQS#acquireQueued

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  1. 进入死循环,进入node的前一个节点,如果是头节点,会尝试再获取一次锁,获取成功返回。这里算是设计的巧妙之处,因为阻塞当前线程也是在这个方法里,当线程被唤醒,这时候可以调用尝试获取锁,直接获取到锁。
  2. 如果没有获取成功,会调用shouldParkAfterFailedAcquire,该方法主要是监测更新status的值。刚开始ws是0,并不是-1,所以使用CSA把节点的Status改成-1,返回flase。同时再次进入死循环。这时候shouldParkAfterFailedAcquire返回的是true,执行parkAndCheckInterrupt
    AQS 定义了5个队列中节点状态:
1.值为0,初始化状态,表示当前节点在sync队列中,等待着获取锁。
2.CANCELLED,值为1,表示当前的线程被取消;
3.SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
4.CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
5.PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
  1. parkAndCheckInterrupt主要是调用LockSupport.park(this);,使当前线程阻塞。

四、解锁逻辑

AQS#release

    public final boolean release(int arg) {
    // 尝试进行解锁
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
            // 执行locksupport.unpark
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

AQS#tryRelease

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
  1. 获取state,然后减一,如果等于0,说明可以释放锁,返回true
  2. 获取等待队列头结点,如果头节点不为空,并且不是0的时候,进行unparkSuccessor唤醒头结点的下一个节点操作。同时会把节点状态改为0。唤醒完成之后,会继续执行AQS#acquireQueued的步骤一,使当前线程抢占锁,同时进行出队。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逸羽菲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值