本次RenntrantLock分析主要包括如下模块:
-
ReentrantLock锁结构
-
公平及非公平体现之处
-
lock流程
-
unlock流程
-
condition模块
一:ReentrantLock源码框架如下所示(第一次画这种图,可能不正规,可以百度去看看框架图)
主要结构如上图
reentrantlock实现了lock接口的lock,unlock等主要方法,通过其内部类syc继承abstractQueueSynchronizer进行实现的。
abstractQueueSynchronizer的作用主要是为lock失败添加了一个队列,以及相关操作,其内部主要也是两个内部类,Node以及Condition
abstractQueueSynchronizer进行的一个安全性添加队列以及删除节点(准确的说不是删除,只是将其引用变为null,交由虚拟机GC时删除),对应于加锁失败以及唤醒锁,获得锁操作。
二:公平以及非公平体现之处:
首先要知道公平以及非公平的区别,才能了解源码实现的区别,知道为什么要这样设计。
一般理解为:在对reentrantlock锁定的资源进行多线程竞争时,未竞争到的会被添加到AQC的同步队列(只有调用lock方法,失败才会添加队列中,trylock只会返回true或者false。AQC还有一个等待队列是Condition使用的)。那么非公平以及公平的区别就在于同步队列获取锁以及后续第一次来尝试获取锁的线程之间的竞争了。
公平锁在锁被释放之后,Release操作只会唤醒同步队列的第一个waitstatus为<=0的Node所对应的Thread。
重点来了:公平锁只会让这个被唤醒的Thread去获取锁,而与其竞争的线程(此时恰好有其它线程突然也跑来获取这个资源)只会被添加到队列的tail,作为尾节点,一直睡眠到前面的都释放了,才会被唤醒尝试获得锁(前提是这个线程是Lock后被添加到同步队列中而不是trylock)
非公平则是这个被唤醒的Thread和其他进来获取资源的Thread进行争夺,这时就不会保证被唤醒的线程一定会获取到锁,相反其获取到锁的概率会比较小,因为Release的源码是先释放锁,再去唤醒它的下一个节点。别人是释放锁直接获取,你是释放锁之后再去唤醒你,然后才是获取,自然就慢了,造成饥饿锁情形出现,但是也有好处,就是提高了效率,确保唤醒的线程获取到锁会经过以下操作,浪费时间。直接放源码了,步骤原理,在后面实现lock时进行讲解。
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())//唤醒的线程在这儿被唤醒后,会继续通过这个死循环进行获取锁,先获取上一节点,再判断是否为head,再去执行 //tryacquire,唤醒线程获得锁之后,还要进行set操作等等后续操作。而 非公平锁直接就tryacquire效率会很高
interrupted = true; //(本来以为是切换上下文资源,可是对于同一个进程下,切换不同的线程是不需要切换上下文的)
}
} finally {
if (failed)
cancelAcquire(node);
}
}
那么公平锁如何实现唤醒的线程能够获得锁呢?其实很简单,使和唤醒的线程竞争的其它线程获取不到锁,那么你效率再差,那么唤醒的线程就能获取到锁了,使得效率变低了(本来其它的线程都要获取到锁了,却因不是同步队列唤醒的第一个被阻止了,只能慢慢等到唤醒的线程获取到锁,导致效率低)
三个方面(个人认为)
还是上代码:
1:lock获取锁时,不管公平锁还是非公平锁都会调用AQS中的acquire,再调用各自的tryqcquire获取锁,但是两者在调用acquire之前的操作是不一样的,这也是公平性 的区别。
final void lock() { //这是非公平锁,在lock时,会先进行compareAndSetState(在这儿就没有公平性),失败才会调用acquire,从而调用tryacquire。
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
final void lock() {//这是公平锁,在lock时,会直接进行acquire,而不是compareAndSetState
acquire(1);
}
2:在调用AQS的acquire时,都会调用到各自的tryacquire,这就是真正的公平性体现地方了
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&//这就是公平锁确保唤醒的线程能获取到锁的原因,其它的线程在这儿返回true,获取不到
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
public final boolean hasQueuedPredecessors() {//判断这个获取锁的线程是否是等待时间最长的或者这个队列是否为空,只有这两种情况返回false,没有人比我等的时间还长
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s; // h==t,则是空队列,那么这个正在获取锁的一定能获取到锁,返回false,如果队列不为空,就一定要保证这个唤醒线程获得锁了
//不为空,由于&&是短路的,进行右边判断。
//h.next就是被唤醒的线程,s.thread和正在获取锁的thread不一致,返回true,你就获取不到锁了
return h != t && //(只有返回的是false才会获取到锁)
((s = h.next) == null || s.thread != Thread.currentThread());
}
3:还记得上面的公平性效率问题吗?在那儿也体现了公平性,但是此公平性的体现,并不指是公平锁和非公平锁的区别
//此方法用于竞争失败的Thread在添加到同步队列之后,会继续执行接下来方法
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)) {//公平性还体现在这儿(其实这是同步队列的公平性,不管是非公平还是公平锁,这儿都会保证同步队列的公平性,
//先进先出,真要说是公平锁的公平可能有点问题,主要是体现了公平性),只有你的上一个节点是head你才能尝试获取 //锁,否则就去睡觉去。parkAndCheckInterrupt()会让你去睡觉阻塞,也就是上面的在这儿被唤醒。
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())//唤醒的线程在这儿被唤醒后,会继续通过这个死循环进行获取锁,先获取上一节点,再判断是否为head,再去执行tryacquire,而
// 非公平锁直接就tryacquire效率会很高,而且唤醒线程获得锁之后,还要进行set操作等等后续操作。
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
ok,现在来看看lock的整个流程及其源码分析:(可重入特性也在这儿体现)
手画一张Rentrantlock的lock实现过程(lock主要是添加了获取失败了,调用AQS生成一个同步队列)
lock的主要流程:
再来看看同步队列的生成:(有两个比较有意思的小地方,生成节点添加队列时,如果队列为空,会先生成一个空节点作为HEAD节点,第二个是添加队列时会先保证prev指向tail,再通过cas设置tail,最后设置原tail的next指向添加节点,保证了并发时的prev正确性,一定要保证这个。为后面的唤醒做准备)
下面就直接通过代码对这个流程一一进行剖析
就直接以NonfailSync 非公平锁 进行原型解析了:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))//在前面公平性已经说了,非公平锁会直接CAS设置,看能不能获取到
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//接下来就会调用AQS的acquire方法了
}
假设没有获取到锁(获取到了就没下面步骤进行分析了),会调用AQS的acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) && //会先调用reentrantlock的内部类重写的tryacquire方法,获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//获取到锁也就不执行下面的了,只有没有获取到锁,才会执行&&左面的方法,添加队列,尝试获取锁,是否进入等待阻塞状态
selfInterrupt();
}
先来看看非公平锁的tryacquire
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);//调用父类的nonfairTryAcquire方法,公平锁会自己实现tryAcquire
}
final boolean nonfairTryAcquire(int acquires) {//父类syc的nonfairTryAcquire
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current); //仍然是日常尝试获取锁操作
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires; //这儿是可重入的地方,获得锁的可以再进去,state➕1就可以,进几次加几次,释放锁也要相应的释放次数
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false; //没有获取到锁
}
现在就把acquire的tryacquire进行分析完全了,由于没有获取到锁,进入 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法
先来分析里面的参数addWaiter(Node.EXCLUSIVE)此方法是将获取锁失败的线程包装成一个Node节点,添加到同步队列中去。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);//将线程包装为一个Node节点,具体看AQS的内部类Node
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;//队列的尾节点
if (pred != null) {
node.prev = pred; //一定要先保证prev节点的正确性
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node; //已经作为尾节点添加进去了
}
}
enq(node); //
return node;
}
//如果为空就调用enq,都是通过compareAndSetHead保证正确性
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize 再进行一次判断是否为空,有可能又添加了一个,此时不为空。还有一个主要因素是:在后面等待队列唤醒后,添加到同步队列也是调用这个 //方法,所以这个方法要对tail是否为空进行判断,才是一个完整的添加。
if (compareAndSetHead(new Node())) //为空,创建一个新的节点作为空节点,因为同步队列head一般都是获取到锁的,队列为空时,添加一个节点状态为0的节点 ,在下面 //源码分析时,这个节点状态会被设置为-1 。
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
//添加之后,就调用acquireQueued尝试获取锁,没有获取到锁就去休眠,进行阻塞。shouldParkAfterFailedAcquire这个方法使得prev指向的节点的waitstatus一定是signal,而不是cancelled(值为1 >0)。
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);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release //找出上一个节点为SIGNAL的,否则如果>0就去更新prev节点指向最近的一个waitstatus不>0的其实也就是不为 1的 //节点,此时返回false,由于这个方法所在的方法体是死循环,会再进来,此时不会>0,就去cas设置为signal
//在死循环进来就是signal了,就会执行下面方法,阻塞住了。如果开始状态小于或者是=0就直接设置为signal(在 //队列为空时添加的head就属于waitstatus=o的情形,也会帮其设置为-1)。这 个节点没有失效,只有为1时,才会 //被 失效
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
唤醒后就会尝试获取lock
private void setHead(Node node) { //获取之后就会把node设置为head节点
head = node;
node.thread = null; //消除thread引用
node.prev = null; //消除prev引用
} //next引用会在后面thread获取锁后,帮助head节点的next=null,等待fullgc回收
此时一个完整的lock流程就结束了,没获得锁就会通过以上流程加入到队列中,并被阻塞
下面就为非公平锁分析一下unlock释放锁源码
public void unlock() {//reentrantLock调用unlock时,还是在调用AQS的release方法
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) { //会先调用syc的tryrelease方法
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
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; //释放完全就会使得c==0,重入锁,应该一直减1,减进来的次数,知道c==0.
setExclusiveOwnerThread(null); //释放线程
}
setState(c); //更新state ,不需要是CAS设置,因为此时是获取到锁的线程的操作,只有set成功之后,才没哟锁了
return free;
}
tryRelease执行完后,再回过头来看看release方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); //在释放锁之后就可以去唤醒锁,此时没有任何线程获取到锁了,这也是为什么会出现非公平时,唤醒线程的锁获得锁的概率比较小。
return true;
}
return false;
}
unparkSuccessor(h); 是一比较重点的地方了
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus; //上文提到过,添加队列时,队列为空会先添加一个新的节点 private Node enq(final Node node) {
//这个节点状态为-1,就会唤醒下面的线程,本来是0,但是在睡眠时更新为-1
//,唤醒它的下面状态<=0的 for (;;) {
Node t = tail;
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); //就会被唤醒,进入到上面源码分析时讲到的阻塞睡眠的地方唤醒,进行获取锁操作
}
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()) //会在这唤醒,死循环,会继续尝试获取锁,公平锁,就会保证一定会获取到的,因为P==head时会tryacquied,而此时没有锁了,就保 //证了一定会获取到的
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
公平性的和非公平性的也差不多,下面就说说condition了
Condition作为renntrantlock的特点,其实也是AQS的内部类,其也为调用Condition的线程提供了一个队列去进行添加,删除(fullgc),对应着await和signal操作。
也涉及到了Node的NEXWaiter属性通过这个使得conditin对应的等待队列各个node节点相连接,引用。
Node的prev和next就是同步队列的各个Node节点的互相连接,引用。
condition主要两个作用,await以及signal,对应等待队列的添加,删除(fullgc)直接通过源码分析了。
await的源码分析:
先看看步骤流程图:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); //添加等待节点
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) { //判断是不是在同步队列里
LockSupport.park(this); //阻塞住 等待队列和同步队列差不多。都是一个死循环里阻塞,唤醒,这种操作
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters(); //同步队列和等待队列互相转换,同步队列里的Node获取锁被await,会添加到等待队列里,然后tryrelase释放锁唤醒下一个Node节点
if (interruptMode != 0) //等待队列唤醒后会先尝试获取到锁,然后把等待队列更新,删除掉(等待队列也是所有引用置为空,同步队列是下一个获取锁的把上一个next节 //点引用为空,等gc回收掉)
reportInterruptAfterWait(interruptMode);
}
开始对await里每一个方法进行源码分析
addConditionWaiter添加等待节点分析
private Node addConditionWaiter() {
Node t = lastWaiter; //等待队列只有nextWaiter作为等待队列Node之间的引用
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters(); //Node节点不是等待队列,也就是说在为尾节点添加节点时,有可能那个尾节点被唤醒或者其他操作时,不处于等待状态了,要先将原等待队列进行一次 //扫描,将不是等待状态的全都删除掉,这个和同步队列不太一样,同步队列只要保证自己的prev节点是tail就可以了,后面的有效性会在后面判断是否 //阻塞时判断更新,这是因为同步队列有两个prev和next指针,等待队列只有一个nextwaiter指针,不能够通过prev指针回溯去检查。
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node; //这儿添加的时候,如果是空队列也直接把Node作为首节点,而同步队列是先生成一个空队列作为头部。
else //主要是同步队列默认首节点是获取到锁的,唤醒其next节点,如果开始没有这个缓冲,那么首节点不会被唤醒,尝试获取锁,而等待队列则不同, //signal是直接对firstWaiter进行唤醒的,同步队列是唤醒下面一个节点。
t.nextWaiter = node; //只要设置下一节点指针就可以了,单向链表
lastWaiter = node; //再更新尾节点指向
return node;
}
添加进去后就会释放锁了,所以添加节点操作一定是安全的,就不是和同步队列添加时需要CAS设置了,这也是一个区别
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState(); //这儿释放和前面unlock释放不一样,unlock释放的参数是1,也就是说重入锁需要多次进行release才能释放掉锁,而await是一点要保证不管是不是重 //入锁都要保证完全释放掉,所以release参数一定是getstate的参数,就会完全释放掉
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
释放锁之后,此时此线程就没有锁了,那么其他线程机会获得锁,由于release里会有个方法唤醒同步队列的下一个节点,所以就会有接下来的操作,判断添加的节点是否在同步队列中,防止获得锁的线程通过signalall方法又唤醒了。
不再同步队列中,就直接把它在等待队列中休眠阻塞,知道别的线程来唤醒,就不上代码了,和前面的同步队列阻塞是一样的
如果在同步队列中,那么刚刚在等待队列中添加的节点就要被删除掉(也就是去除引用,等待gc回收)
在删除掉还会有一个步骤就是尝试获取锁,因为你被唤醒了自然就回去获取锁了,而不是直接把锁给你,失败了,就会和上面lock一样,进入休眠阻塞,等待前面的Node成为ead节点并且释放锁
下面就是删除锁的步骤了
private void unlinkCancelledWaiters() {
Node t = firstWaiter; //由于等待节点不是双向链表,只有首尾节点以及nextwaiter进行连接,所以和同步队列里保证有效性有区别的
Node trail = null; //先设置一个中间变量 trail作为后面有效性链表的尾节点
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) { //判断是否是等待状态
t.nextWaiter = null; //不是等待状态,就会将其nextWaiter清除引用,等待gc回收
if (trail == null) //再判断下一个节点,由于第一个节点就是错误的,所以此时尾节点仍然是null
firstWaiter = next; //那么next节点自然就是首节点了
else
trail.nextWaiter = next; //如果tail不为空,说明队列已经有尾节点,不为空了,自然就把下一个节点添加到尾节点上。
if (next == null) //tail不是尾节点,只是定义的一个中间变量作为尾节点,这样知道最后next为空,队列检查完毕了,就把tail赋值给尾节点lastwaiter了
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
同步队列保证有效性是线程添加休眠时,会保证上节点prev的有效性,释放时,会保证next节点有效性。
等待队列不是双向的,只能一次性unlinkCancelledWaiters方法解决其整个队列有效性
再看下判断是否在同步队列的方法
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node); //主要是这个方法
}
private boolean findNodeFromTail(Node node) {
Node t = tail; //tail是同步队列尾节点
for (;;) {
if (t == node) //判断等待队列刚添加的节点和同步队列尾节点是否相等,不等就去对同步队列进行后序遍历,查询
return true;
if (t == null)
return false;
t = t.prev;
}
}
await方法基本介绍完全了,下面介绍一下signal方法
public final void signal() { //signal主要就是对等待队列的firstWaiter进行 doSignal(first)。
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && //一直唤醒,知道等待队列有一个节点唤醒就结束
(first = firstWaiter) != null);
}
//这就是真正的对等待队列节点唤醒操作了
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node); //这儿就是前面说的,等待队列和同步队列的转换,唤醒后,会添加到等待队列中去
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) //如果p是一个取消了的节点(ws>0),或者对p进行CAS设置失败,则唤醒node节点,让node所在线程进入到 //acquireQueue方法中,重新进行相关操作,也就是此线程添加到等待队列时,会阻塞的地方唤醒,发现此时 //该节点已经在同步队列中了,就会执行acquireQueued方法,失败就去阻塞,并且设置相关的waitstatus,否则 //的话就获取到锁了
//否则,由于该节点的前驱节点已经是signal状态了,不用在此处唤醒await中的线程,唤醒工作留给CLH队列中前驱节 //点 ,会减少不必要的park以及unpark操作。
LockSupport.unpark(node.thread); //这儿就是唤醒操作了
return true;
}
注意的几个地方:
不管是reenTrantlock还是AbstractQueuedSynchronizer都是对内部类Node或者是sync进行相关操作,这种思想学学。
同步队列以及等带队列的区别,一个是双向的,使用了Node的prev和next节点,一个是单向的,只使用了Node的nextWaiter节点。
两个队列的删除也不是传统意义上的list等等删除,通过清除其引用,等待GC回收。
两个队列的添加由于其使用场景的变化,同步队列在空队列添加时,会先生成一个节点,添加上去,作为head,然后再添加线程对应的节点、,等待队列则是直接添加节点,在空队列时,没有先添加一个傀儡节点这种操作,只会把其作为firstwaiter。
两个队列添加时还有一个小区别,等待队列由于添加时是获取锁,安全的,在释放锁后,会判断是否又被唤醒了,唤醒后,会直接对整个等待队列进行有效性的检查,把不是等待状态的全都移除,而同步队列,在添加之后,有一个判断是否应该阻塞的方法中,直接对其prev进行有效性判断,而不是整个队列,在释放锁,唤醒时,才会对其next节点进行有效性判断,这个也是和等待队列的区别,还是在于结构的不同,一个是单向一个是双向。
还有很多小地方进行学习。。。。。。