图解AQS原理之ReentrantLock详解-非公平锁 (4)

前面代码中可以看到,它有一个快速入队的操作,如果快速入队失败则进行死循环进行入队操作,当然我们上面例子中发现队列其实是为空的,也就是pred==null,不能进行快速入队操作,则进入到enq进行入队操作,下面看一下enq方法实现,如下所示:

private Node enq(final Node node) { for (;;) { //死循环进行入队操作,直到入队成功 Node t = tail; //获取尾节点 if (t == null) { // Must initialize //判断尾节点为空,则必须先进行初始化 if (compareAndSetHead(new Node()))//生成一个Node,并将当前Node作为头节点 tail = head; //head和tail同时指向上面Node节点 } else { node.prev = t; //设置入队的当前节点的前节点设置为尾节点 if (compareAndSetTail(t, node)) { //将当前节点设置为尾节点 t.next = node; //修改原有尾节点的下一个节点为当前节点 return t; //返回最新的节点 } } } }

通过上面入队操作,可以清晰的了解入队操作其实就是Node节点的prev节点和next节点之前的引用,运行到这里我们应该能看到入队的状态了,如下图所示:

图解AQS原理之ReentrantLock详解-非公平锁

如上图可以清晰的看到,此时拥有锁的线程是Thread0,而当前线程是Threa1,头节点为初始化的节点,Ref-707引用地址所在的Node节点操作当前操作的节点信息,入队操作后并没有完成,而是继续往下进行,此时则进行acquireQueued这个方法,这个方法是不间断的去获取已经入队队列中的前节点的状态,如果前节点的状态为大于0,则代表当前节点被取消了,会一直往前面的节点进行查找,如果节点状态小于0并且不等于SIGNAL则将其设置为SIGNAL状态,设置成功后将当前线程挂起,挂起线程后也有可能会反复唤醒挂起操作,原因后面会讲到。

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) && //设置waitStatus状态 parkAndCheckInterrupt()) //挂起线程 interrupted = true; } } finally { if (failed) cancelAcquire(node); //取消操作 } }

前面的源码可以看到它在acquireQueued中对已经入队的节点进行尝试锁的获取,如果锁获得就修改头节点的指针,如果不是头节点或者争抢锁失败时,此时会进入到shouldParkAfterFailedAcquire方法,这个方法是获取不到锁时需要停止继续无限期等待锁,其实就是内部的操作逻辑也很简单,就是如果前节点状态为0时,需要将前节点修改为SIGNAL,如果前节点大于0则代表前节点已经被取消了,应该移除队列,并将前前节点作为当前节点的前节点,一直循环直到前节点状态修改为SIGNAL或者前节点被释放锁,当前节点获取到锁停止循环。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * 此节点已经设置了状态,要求对当前节点进行挂起操作 */ return true; if (ws > 0) { /* * 如果前节点被取消,则将取消节点移除队列操作 */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * waitStatus=0或者PROPAGATE时,表示当前节点还没有被挂起停止,需要等待信号来通知节点停止操作。 */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }

上面的方法其实很容易理解就是等待挂起信号,如果前节点的状态为0或PROPAGATE则将前节点修改为SIGNAL,则代表后面前节点释放锁后会通知下一个节点,也就是说唤醒下一个可以唤醒的节点继续争抢所资源,如果前节点被取消了那就继续往前寻找不是被取消的节点,这里不会找到前节点为null的情况,因为它默认会有一个空的头结点,也就是上图内容,此时的队列状态是如何的我们看一下,这里它会进来两次,以为我们上图可以看到当前节点前节点是Ref-724此时waitStatus=0,他需要先将状态更改为SIGNAL也就是运行最有一个else语句,此时又会回到外面的for循环中,由于方法返回的是false则不会运行parkAndCheckInterrupt方法,而是又循环了一次,此时发现当前节点争抢锁又失败了,然后此时队列的状态如下图所示:

图解AQS原理之ReentrantLock详解-非公平锁

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyffxz.html