ReentrantLock


ReentrantLock 

重入锁的lock和unlock。
ReentrantLock.lock ----> Sync.lock -----> AbstractQueuedSynchronizer.acquire --> Sync.tryAcquire.

waitStatus:表示节点的状态,其中包含的状态有:
CANCELLED:值为1,表示当前节点被取消;
SIGNAL:值为-1,表示当前节点的的后继节点将要或者已经被阻塞,在当前节点释放的时候需要unpark后继节点;
CONDITION:值为-2,表示当前节点在等待condition,即在condition队列中;
PROPAGATE:值为-3,表示releaseShared需要被传播给后续节点(仅在共享模式下使用);
0:无状态,表示当前节点在队列中等待获取锁。


1. lock方法:

总的来说,lock方法分成两步:
第一步:取锁,取成功了直接返回
第二部:如果取锁不成功,就排队

1.1 取锁:

取锁的代码:

ReentrantLock
取锁也分为两种情况:
1. 如果当前没有锁,同时也没有人排队,那么直接取。
2. 如果当前有锁,当时当前线程就是独占锁的线程,代表是重入,也是直接把state+1
如果不符合这两种情况就排队。


1.1.1 直接取锁:
那么第一种情况,什么时候可以直接取呢?看下下面这个hasQueuedPredecessors的方法:

ReentrantLock
hasQueuedPredecessors:
1. 如果queue刚创建,head = tail,代表还没有线程排队,也就是没有前驱节点。
2. 如果queue已经创建,head != tail,但是head后是null,代表没有待处理的节点,也就是没有前驱节点。(也就是下图中箭头所指的代码之处所发生的并发)
3. 如果queue已经创建,而且有线程排队,同时,当前线程就是排在最前面的那个,相当于正在处理的线程重入。也是没有前驱节点。

这三种情况代表没有前驱节点。可以直接拿到锁。

ReentrantLockReentrantLock

1.2 排队:

排队先把Node加到尾部
1. 再看一眼是不是可以加锁了,可以的话,就取锁,然后把head设置为当前的node
2. 如果不能加锁,就park


park的操作结合thread的block状态。

ReentrantLock

ReentrantLock

1.2.1 park的条件:
判断是否能够park。
1. 如果前驱节点是signal的wait状态,也就是前面那个节点做完了会通知我的情况下,可以park。
2. 如果前驱节点的wait状态>0 ,也就是取消的状态,那么从前驱节点往前找,直到waitStatus<=0(也就是找到第一个非cancelled的节点),然后设置找到的节点为前驱节点。
3. 其他的情况,就是0或者3的情况,那就设置前驱节点的wait状态为-1,也就是找到前驱节点告诉他记得提醒我。

ReentrantLock
然后就park了。park后进入一个死循环。
直到当前节点是head后的第一个节点时,重新取锁,结束循环。

ReentrantLock

ps: interruped vs isInterruped 两个方法的区别,interupted 调用后会改变 interrupted的状态,而isInterrupted方法不会。

2. unlock方法:

1. 将state设置为0:
判断当前线程是否是正拥有锁的线程,如果不是就报错。
然后将state-1,如果state-1之后为0,那么久设置state=0,并将排他的线程设置为null。
2. unpark后继节点
    1. 如果waitStatus是signal的,那么久修改waitStatus = 0;
    2. 取后继节点
        2.1 如果后继节点为空,或者后继节点cancelled了,那么从tail开始找,找到离当前节点最近的一个非cancelled的节点。
        2.2 unpark后继节点。

看到了两段有意思的代码:

ReentrantLock

 vs 

ReentrantLock
刚开始看,以为这两段代码的逻辑是一样的,还在想为什么要写两种模式。
仔细一看,发现还是不同。
左边的是unlock里的代码,是从tail开始找,找到离当前节点最近的(最后一个)非cannelled的节点。
右边是lock的代码,也是从前驱节点开始找,找到离前驱节点最近的(第一个)非cannelled的节点。

看大佬的代码还是挺有意思。
全程没有用到synchronize,全部用的cas,做成了一个锁。

cas使用的地方:
在tryAcquire取锁的时候,setState用了cas。
addWAiter,append到tail的时候用了cas。
enq,初始化queue设置head的时候,append到tail的时候,用了cas。
shouldParkAfterFailedAcquire,设置前驱节点waitStatus的时候,用了cas。

用通俗的话讲,aqs就是一群人在排队。感觉是结合了CLH和MCS两种队列的模式。

CLH排队取钱:
A过来排队,发现前面没人,就直接到窗口办理了。
这时候B来了,排在了A的后面,发现A还没弄完,那就自己玩玩手机,时不时看看A搞完了。
这时候A搞完了,B看到A搞完了,就来到窗口前办理。

MCS依然还是排队:
1. A来排队,没有人,直接取钱
2. B来排队了,发现有人,就跟A说,你后面是我哈,等会搞完了叫我
3. A取完了,喊了一下B,B继续取

而AQS也是排队:
1. A来排队,没有人,直接取钱
2. B来排队了,发现有人,就跟A说,你后面有人,是我,记得叫我,我去睡觉了(park)。
3. A取完了,喊了一下B,B再看一眼前面有没有人,没有人,再去办理。

重点在哪里呢?
1. 有没有人正在取钱,是state决定的,0就是没有人,>0就是有人。
2. 排队,第一个创建quque的时候,head是个new Node,等到第一个Node上去后,head = node了。也就是说,第一次head是没有线程的,后续都有。
3. park,线程阻塞状态。
4. 前驱节点的waitStatus,决定是否需要signal下一个node。