JVM中显示锁基础AbstractQueuedSynchronizer

在研究AbstractQueuedSynchronizer的时候我是以ReentrantLock入手的。所以理所当然会设计到一些ReentrantLock的方法。因为网上已经有很多关于AQS的文章了,所以这篇文章不会特别详细的去记录类的实现,主要是记录几个我觉得需要主要的点。

1、阻塞队列实现
AbstractQueuedSynchronizer用一个Node队列来实现线程阻塞。处理当前正在执行的线程,后续的所有的线程都会进入到这个虚拟的CLH队列。下面该图是我根据源码画的一个链表队列。head是一个空对象,也就是这个对象是没有thread的,后续的thread都会添加到tail。进入到这个队列的thread都会被操作系统挂起,等正在执行的thread被释放后操作系统会唤醒被阻塞的head的next节点的线程,具体的唤醒方法在unparkSuccess函数,下面会有所分析。

代码的实现思路图解
为了方便理解,我粗略的用word画了一个代码流程图,包含lock和unlock方法。

锁的实现分析
当我们跟踪lock代码的时候,在队列第一次创建的时候会执行这个enq函数:

 private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

仔细看下这个函数会发现,第一个节点是默认给的,在代码第5行,也就是一个空节点new Node()。然后接下来就是把新建的节点插入到tail。
在进入队列后,还没被阻塞之前,会进行一次判断,判断当前node的prev节点是不是head。为什么不直接判断当前节点是不是head,以上的图片已经说明清楚了,head节点的thread是null的,也就是head是默认生成的。为什么要这样做?这样做的好处是什么我还暂时还没想到。如果有网友知道麻烦留言告诉我下哈。

  final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();//1
                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);
        }
    }  

但是在上面注释1中获取当前node的前置节点。如果p是head的话,那么node的线程会尝试获取一次锁tryAcquire。

 final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }  

如果还是获取不到锁,那么久当前线程阻塞。实现方法是调用 LockSupport.park,改方法会直接调用操作系统的内置方法来实现线程阻塞:

 private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }  

2、线程唤醒
当依噶线程unlock后就会释放锁,那么之前争夺锁的线程都被阻塞挂起了,现在要做的一件事情就是唤醒挂起的线程。当然不是把所有的线程都唤醒,那么它的唤醒规则是怎么样的呢?显然因为阻塞锁是一个FIFO的队列,所以肯定是从head开始。

  public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }  

开始的时候我们已经说过head是空的,所以唤醒要从第二个节点开始,看下面Node s = node.next;就知。

  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;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        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);
    }  

3、Node几点状态码
把线程要包装为Node对象的主要原因,除了用Node构造供虚拟队列外,还用Node包装了各种线程状态,这些状态被精心设计为一些数字值:

SIGNAL(-1) :线程的后继线程正/已被阻塞,当该线程release或cancel时要重新这个后继线程(unpark)

CANCELLED(1):因为超时或中断,该线程已经被取消

CONDITION(-2):表明该线程被处于条件队列,就是因为调用了Condition.await而被阻塞

PROPAGATE(-3):传播共享锁

0:0代表无状态

参考:
http://www.open-open.com/lib/view/open1352431606912.html

posted @ 2016-06-20 22:29  小霖2012  阅读(3090)  评论(0编辑  收藏  举报