对于ReentrantLock我相信对于做Java的朋友应该不会陌生,它是juc并发工具包下面的一个并发工具类,对于一些存在资源竞争的情况下,我们经常会用到,和java关键字synchronized不同的是,ReentrantLock提供了更好的灵活性以及实现了公平和非公平锁,还有超时的概念,那ReentrantLock底层是如何实现的呢,那我们通过源码去深入了解他实现的原理,我们才能更好的使用。
首先我们看我们平常使用ReentrantLock的例子
这里通过构造方法初始化了ReentrantLock,我们点击进入
ReentrantLock里面维护了一个Sync的内部类,Sync继承了AbstractQueuedSynchronizer抽象同步器,这个AbstractQueuedSynchronizer定义了锁的一些基本架构,juc大部分的同步工具类都是基于这个类实现的,这里就不做过多的阐述了,后面单独出来讲讲这个抽象同步队列器,从上面的代码我们可以看到通过ReentrantLock的构造方法,初始化成员变量Sync的是其子类之NonfairSync这个类是实现非公平的锁的功能,我们从最开始的加锁的代码跟进
加锁的时候会调用的sync.lock()方法,前面通过构造方法初始化的时候这个sync的成员变量对应的是NonfairSync这个实现类,所以这里调用的是NonfairSync的lock方法
一进来的时候通过compareAndSetState这个cas操作,看能不能将AQS里面维护的一个int类型的state成员变量,如果能够成功将它的值改为1,就代表获取锁成功,并且将持有锁的线程应用赋值为当前线程,如果失败则调用acquire(1)这个分支,我们继续跟进源码
这里首先会再一次去尝试获取一下锁资源,
这里最终会调用到nonfairTryAcquire这个方法里面,这里面主要的逻辑就是,先获取这个全局变量state的值如果是0代码锁资源还没有被其他线程获取,直接通过cas操作,如果能够把state从0变到1,代表获取锁成功,直接返回true的标识,第二种情况state不为0的情况,并且当前进入的线程正好是锁的持有者,就直接将state加1操作,这里就是实现可重入锁的关键代码逻辑,其余的情况返回false,我们将代码返回到之前调用之初的地方
这个tryAcquire方法会最终调用到上面nonfairTryAcquire方法里面,如果nonfairTryAcquire返回false代表获取锁资源失败,这里取反操作第一个条件为true,就会进入acquireQueued(addWaiter(Node.EXCLUSIVE), arg)代码里面,这里会先调用addWaiter方法
首先会用当前线程初始化一个Node对象,然后将tail赋值给pred,第一次进来的时候pred肯定为空,就会走enq(node)代码逻辑,继续点进去看看这个enq方法
很明显这里通过一个自旋操作(也就是死循环),第一次进来的时候因为tail为空,肯定会走到上面的分支,然后通过cas操作设置一个头节点对象,这里head成员变量就有值不为空了,最后赋值给tail尾节点,第一轮循环结束,进入第二轮循环的是,此时Node t = tail 此时的t就不为空了,会走else里面的代码逻辑,先是将当前传入节点的前驱节点指向头节点,然后通过cas操作将当前节点加入到队尾部,然后直接return退出自旋,最终返回到调用的初始地方
这里最终会调用到acquireQueued这个方法里面会首先判断当前节点的前驱节点是否为头节点,如果是头节点的话,会尝试再去获取一次锁,如果获取成功就直接结束,如果获取锁资源失败就会接着往下面走
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true;
这里首先会进入shouldParkAfterFailedAcquire这个方法,
这段代码的作用就是将成员变量waitStatus从0改到signal -1的状态,后续能否唤醒线程的条件,然后返回true结束,最终走完返回到
因为最终返回的true,这里会进入parkAndCheckInterrupt这段代码里面
很明显这里就是调用的java工具类LockSupport的park方法,进行阻塞,到这里整个加锁的逻辑就已经结束了,后面再讲讲是当前线程释放资源后是如何唤起阻塞线程的