JVM本地锁(二)ReentrantLock可重入锁源码解析

本文详细剖析了Java并发工具类ReentrantLock的工作原理,从lock()和unlock()方法出发,解释了如何通过CAS操作实现锁的获取与释放,阐述了可重入锁的实现细节,包括线程的加锁、解锁过程以及等待队列的管理。通过对源码的解读,帮助读者深入理解ReentrantLock的内部机制。

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

什么是可重入锁呢
顾名思义,就是可以重复进入的锁,学过操作系统或者计组的可参照理解pv,或者多重中断。

demo1(){
        lock(); //第一次锁
        demo2(){
            lock(); // 第二次锁
            unlock(); 
        }
        unlock();
    }

ReentrantLock

lock 加锁

在这里插入图片描述

1. ReentrantLock.lock()

直接从lock()入手翻阅源码

public void lock() {
        sync.lock();
    }

它调用的是sync.lock();

2. sync.lock()

在这里插入图片描述
在ReentrantLock初始化时,默认是非公平锁,有参构造true则是公平锁

非公平锁:来个线程就先试试能不能插队,不能插队才去后面排队
公平锁:线程都乖乖去后面排队去,不准插队

总而言之,sync就是个内部非公平/公平锁。

再往下看,由于lock()是抽象方法,而sync默认是非公平锁。
在这里插入图片描述

调用unfairSync.lock()

3. unfairSync.lock()

final void lock() {
		// 若CAS抢到锁,记录设置当前线程
       if (compareAndSetState(0, 1))
          setExclusiveOwnerThread(Thread.currentThread());
       else
       // 若没抢到锁
           acquire(1);
}

点击compareAndSetState

在这里插入图片描述
这就是CAS获取锁,unsafe是JDK中用于硬件实现CAS的操作。
不用管它,只需要理解这里就是CAS操作。

若state==0,则更新为1,并且设置好排他线程。即该线程成功抢到锁

那如果没抢到锁呢

4. AQS.acquire(1)

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

先执行tryAcquire(),可以理解为再次抢锁。

  • 如果成功,返回为true,再加个!,成了false,后面就不用执行
  • 如果失败,则执行后面acquireQueued(),即进入等待队列

这里nonfairSync重写方法,直接调用nonfairSync.tryAcquire(1)

在这里插入图片描述
继续往下调用nonfairTryAcquire(1)

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 如果state=0,再重新尝试一下看能不能抢到锁
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 如果state不为0
            // 如果这个线程就是之前已经抢到锁的那个,它又要加锁,重入!
            else if (current == getExclusiveOwnerThread()) {
            	// state递加上去
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

简单理解就是

  • 如果state==0,再重新CAS抢救一下,看能不能抢到锁,抢到了那就成功
  • 如果state不为0,说明已经被抢了。但是如果那个抢到锁的线程是自己,自己又重入了,那state+1,再次加锁,成功。
  • 如果那个抢到锁的不是自己,加锁失败。

如果加锁失败,则方法返回到AQS.acquire(1),还需要执行if后面的判断。
简单看下addWaiter();

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        // tail即尾结点
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

这里维护着一个双向链表,简单来说就是把结点放到链表的尾部,并且更新尾结点。
加锁失败了,把这个线程放在表尾,乖乖排队吧。

这就是加锁的所有过程。

unlock 解锁

1. ReentrantLock.unlock()

在这里插入图片描述
很明显,看来这里是将之前的state一个个减1减回来

2. AQS.release(1)

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease也就是解锁了

3. syn.tryRelease(1)

在这里插入图片描述
就是将state-1,进行解锁操作。由于可能被可重入了,state-1后不一定为0;如果为0则将记录的线程清空。

解锁很好理解,就不详细赘述了。

总结

lock:

  • CAS获取锁,若没有线程占用锁(state==0),加锁成功并记录当前线程是有锁线程(两次,开始一次,acquire()中又一次)
  • 若state值不为0,说明锁已经被占用,则判断当前线程是否是有锁线程,若是则重入(state+1)
  • 否则加锁失败,入队等待

unlock:

  • 判断当前线程是否是有锁线程,不是则抛出异常
  • state-1, 若-1后state值为0则解锁成功,返回true
  • 若-1后state不为0,则返回false
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

范大

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

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

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

打赏作者

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

抵扣说明:

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

余额充值