RenntrantLock中lock过程解析
1 | public static void main(String[] args) { |
公平锁与非公平锁初识
直接点进lock()方法
1 | public void lock() { |
直接调用sync的lock方法,Sync是RenntrantLock的一个内部类,Sync这个lock方法是抽象方法,实则分别调用的是它的两个子类的lock方法。


FairSync
1 | final void lock() { |
NonfairSync
1 | final void lock() { // 非公平锁,先尝试获取锁,这里和公平锁的区别可以看到了吧,公平锁直接执行else中代码!!!所以这个效率比公平锁高 |
代码中的注释写出了非公平锁和公平锁的区别,好比超市排队结账,非公平锁就是买完东西后一来就直接要插队,要去结账(插队操作就是CAS操作),然后发现此时收银员正在给另外一个人结账(也就是锁被其他线程占用),那就乖乖排队,执行acquire(1);公平锁就好比一个老实人,乖乖排队直接acquire(1)。
acquire方法
它们都要进入acquire方法,这个方法是AQS类中的一个方法。
1 | public final void acquire(int arg) { |
它们都会执行tryAcquire(arg)这个方法,这个方法的意思是尝试获取锁,但是这个方法在AQS中并未具体实现,子类来实现这个方法,模板方法设计模式的一种体现。

tryAcquire方法
FairSync中
1 | protected final boolean tryAcquire(int acquires) { // 获取锁成功true,否则false |
NonfairSync中
1 | protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires);} |
1 | final boolean nonfairTryAcquire(int acquires) { |
小结
明白上述操作就应该明白了
- 非公平锁和公平锁的区别?
- RenntrantLock为什么是可重入锁?
- RenntrantLock类的内部结构?
之后读源码就该按照一条线来分析了,上述操作是按照两条线,(公平锁和非公平锁),下面就来具体分析公平锁的加锁是怎么实现的,因为公平锁比非公平锁多一点东西,公平锁弄懂,非公平锁自然也就容易明白了。
详解公平锁加锁过程
前面调用了FairSync中的tryAcquire方法后,如果获取成功返回true,否则返回false。
1 | public final void acquire(int arg) { |
分析acquireQueued(addWaiter(Node.EXCLUSIVE), arg)这个方法,先看其中的addWaiter这个方法,实际上这里你想一下,自己获取锁失败了,结果就是自己要去排队了,就是实际处理更加复杂而已。
addWaiter方法
1 | private Node addWaiter(Node mode) { // 这个方法就是为当前线程排队节点,并且初始化这个队列,这个参数的意思就是你是共享模式(Node.SHARED)还是独占模式(Node.EXCLUSIVE)。 |
我感觉这个实际上说初始化可能不太准确,先看Node结点的成员变量组成都有哪些。(注意并未列出全部只是列出部分当前使用到的成员变量)

1 | // 结点共享模式下的标记 |
这是这个Node类中目前使用到的部分成员变量,从这些元素中就可以看出这个是一个双向链表的一个结点。然后分析AQS中的一些成员变量。

1 | // 头结点 |
知道这些后肯定就认识到了,在这个队列未初始化之前的情况是:

enq方法
继续向下执行,上述addWaiter假如我们未初始化,或者CAS失败,然后会执行enq这个方法。这个方法主要难度在理解这个for死循环,构成了不断自旋操作,使用CAS+自旋设置tail结点。
1 | private Node enq(final Node node) { |

这个操作执行完后那么这个addWaiter方法就彻底执行完毕了,此刻的链表的情况为:

小结
这个图应该很清楚了吧,那个thread那个t1是我随便写的,表示的线程对象。
现在应该明白了addWaiter()这个方法是做什么的了吧!!!

acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法
前面已经了解了addWaiter这个方法,这个方法返回的是当前线程封装后的Node结点。
1 | final boolean acquireQueued(final Node node, int arg) { |
现在来看shouldParkAfterFailedAcquire这个方法
shouldParkAfterFailedAcquire方法
1 | private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { |

假如返回false的话就再次执行循环,尝试获取锁,这就是第二次自旋操作!!!
如果第二次自旋还是获取锁失败的话,又将进入这个方法,此时当前线程的前一个结点的waitStatus为-1,直接返回true,导致parkAndCheckInterrupt()这个方法执行!!!
1 | private final boolean parkAndCheckInterrupt() { |
到这里线程的加锁才真正的结束!!!到现在还有一个方法还未说明,不知道心里想到了吗!!hasQueuedPredecessors()就是这个方法,到现在才说因为要把前面这些弄懂这个方法才比较好明白!!
hasQueuedPredecessors()方法
这个方法是tryAcquire中的判断自己是否需要排队
1 | public final boolean hasQueuedPredecessors() { |

到此非公平锁加锁完毕,非公平锁大致流程也一样,就是除了我上述说的那些不一样之外都相同。
总结
AQS就是用一个volatile修饰的state标志来记录锁相关的状态,加上一个阻塞队列。如果这个标志位是被占用状态然后此线程就阻塞入队,空闲状态就可以获取锁,这里获取锁成功的含义就是你那个线程执行的lock()这个方法能正常返回,然后它自然就可以向下执行了,加锁不就是阻塞住,不让lock()方法正常返回吗。不过在阻塞这个过程中存在很多细节,并不是直接将线程阻塞住(这样的话不就和1.6之前的synchronized差不多啦嘛,当然是在存在线程竞争情况下,非竞争情况下还是lock是Java API级别较快),而是进行自旋操作再次尝试获取锁(不知道你感觉这个1.6版本的synchronized锁升级差不多吗)。加锁就是利用的Unsafe类的park()方法。
然后这里简单总结一下。
- 阻塞队列的第一个结点的线程总是为空。
- 当前持有锁的那个线程总是不在队列当中。
那个第一个结点应该模拟的是当前持有锁的那个线程,就像食堂吃饭排队一样,你能说在窗口打饭的第一个人在排队吗???
只有第二个人才是第一个在排队等待的人,他才有资格询问一下该不该轮到我了。



