ReentrantLock和AQS源码解读系列二

ReentrantLock和AQS源码解读系列二

  • 公平锁的细节hasQueuedPredecessors
  • 取消状态细节cancelAcquire
    • 如果取消的是尾结点
      • 修改尾结点成功
      • 修改尾结点不成功
    • 如果取消的是不尾结点,新前驱是头结点
    • 如果取消的是不尾结点,新前驱也不是头结点

公平锁的细节hasQueuedPredecessors

如果我们设置为公平锁,那么在尝试获取所之前会先判断队伍里有没有人在排队,对应的就是:

    public final boolean hasQueuedPredecessors() {
        Node h, s;
        /**
         * 头结点为空的话,说明队伍都没创建,更不用说排队了,可以尝试获取锁,直接返回false
         */
        if ((h = head) != null) {
            /**
             * 如果头结点的下一个为空,说明只刚创建了队列,头尾都是同一个结点,没人排队,那下面的循环就不执行了,可以尝试获取锁,返回false
             * 如果头结点的一下个不为空,说明有人排队了,但是要看下排队的那个人的状态,
             *      如果状态是取消,那就要开始循环,从队尾开始,一直找到队头位置,尝试找出离队头最近的不是取消状态的结点
             *      为什么是从队尾开始,而不是队头,预备知识二的文章有解析过。
             */
            if ((s = h.next) == null || s.waitStatus > 0) {
                s = null; // traverse in case of concurrent cancellation
                for (Node p = tail; p != h && p != null; p = p.prev) {
                    if (p.waitStatus <= 0)
                        s = p;
                }
            }
            /**
             * 如果找到了那个结点,而且结点中的线程不是当前线程,说明别人在排队了,返回true,
             * 如果找不到,就直接返回false,或者找到了,又正好是自己,当然可以获取锁,说明自己在队伍里,而且是被唤醒或者中断的,尝试获取锁的,所以也返回false,
             */
            if (s != null && s.thread != Thread.currentThread())
                return true;
        }

        return false;
    }

基本上的情况我都注释了,主要还是注意认为有人的情况其实是认为有别人在排队,而不是自己,如果发现那个人是自己,那也不算有人排队,因为自己就是在队列里的,无非是醒来自旋获取锁而已。所以这个机制就避免了插队的可能性。

取消状态细节cancelAcquire

从代码上看,貌似只有发生了异常或者error,而且捕获的是所有的情况,会出现取消状态,其实另外就是用了tryLock超时获取锁,超时了也会取消:
ReentrantLock和AQS源码解读系列二_第1张图片
具体代码:

  private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;
        //释放线程引用
        node.thread = null;

        // Skip cancelled predecessors
        Node pred = node.prev;//寻找状态不为取消的前驱结点,可能是直接前驱,也可能是隔着好几个取消结点的前驱,作为新的前驱
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        // predNext is the apparent node to unsplice. CASes below will
        // fail if not, in which case, we lost race vs another cancel
        // or signal, so no further action is necessary, although with
        // a possibility that a cancelled node may transiently remain
        // reachable.
        Node predNext = pred.next;//获取新的前驱结点的后继结点,可能是node,也可能是个取消结点

        // Can use unconditional write instead of CAS here.
        // After this atomic step, other Nodes can skip past us.
        // Before, we are free of interference from other threads.
        node.waitStatus = Node.CANCELLED;//把结点直接设置为取消

        // If we are the tail, remove ourselves.//如果当结点是尾结点,且设置新的前驱结点为尾结点成功了,就把新的前驱结点的后继结点设置为null,因为是尾结点了
        if (node == tail && compareAndSetTail(node, pred)) {
            pred.compareAndSetNext(predNext, null);
        } else {
            // If successor needs signal, try to set pred's next-link 如果前驱不是头结点,且是有SIGNAL标记的或者是可以设置成SIGNAL的,且线程不为空的,尝试设置后继结点
            // so it will get one. Otherwise wake it up to propagate.
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && pred.compareAndSetWaitStatus(ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)//如果node结点后继结点不为空,且不为取消状态,新的前驱结点的后继结点就是node的下一个结点
                    pred.compareAndSetNext(predNext, next);//这里只设置了新前驱的后继,不过也不一定能设置成功,如果自旋设置有点浪费性能了
            } else {
                unparkSuccessor(node);//如果是头结点,唤醒node的后继
            }

            node.next = node; // help GC //node后继指向自己,断开引用,前驱不能设置,不然可能就断链了
        }
    }

其实就是如果出现一个结点要取消,找到该结点的非取消前驱结点,如果该前驱是头结点,那就唤醒该结点的后继,如果不是,那就把该前驱的后继改为该结点的后继,但是这里并没有把后继的前驱改为新前驱,后继的前驱还是指向node,这里感觉有点奇怪,不过这里修改都是用CAS,也就是可能多线程一起修改,肯定有失败的情况,如果自旋去修改,估计性能就低了,就算不修改其实也没关系,只要连接在,整个队伍的前驱后继信息不丢就行。
为了帮助理解,我还是把几种情况画下图好点。

如果取消的是尾结点

修改尾结点成功

也就是尾结点改了。
ReentrantLock和AQS源码解读系列二_第2张图片

修改尾结点不成功

尾结点没改,不过没关系,后面新来的自然就把尾结点设置了。
ReentrantLock和AQS源码解读系列二_第3张图片

如果取消的是不尾结点,新前驱是头结点

那就要唤醒后继结点`unparkSuccessor(node),最后就变成这样:
ReentrantLock和AQS源码解读系列二_第4张图片

如果取消的是不尾结点,新前驱也不是头结点

ReentrantLock和AQS源码解读系列二_第5张图片
当然前面的都是成功的情况,如果不成功,也没关系,源码里也没有说要自旋一定改了为止,其实这里就是能改最好,改不了,在后续结点阻塞的时候会去把前面取消的清除出去的,所以不用担心,如果在这里强制改了,可能性能不好,看shouldParkAfterFailedAcquire中的一段:
ReentrantLock和AQS源码解读系列二_第6张图片
也就是这样,其实没关系,取消的结点不会影响队伍里了,而且到时候会被GC释放:
ReentrantLock和AQS源码解读系列二_第7张图片
最后,如果取消的不是尾结点的话,就执行了node.next = node;,就只改了后继,前驱怎么没改,一般想着如果要删除,不应该连前驱也改成自己么,其实如果这里改了,就可能出现断链的情况,比如:
ReentrantLock和AQS源码解读系列二_第8张图片
一旦出现这样情况,前驱找不到要唤醒的非取消后继了,后继结点也找不到要标记SIGNAL的非取消前驱了。

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

你可能感兴趣的:(Java并发编程)