美文网首页技术干货java进阶干货
一步步透彻理解Lock的Acquire和Release原理源码

一步步透彻理解Lock的Acquire和Release原理源码

作者: 激情的狼王 | 来源:发表于2018-01-09 16:00 被阅读0次

    Java中已知的锁有两种,一种是synchronized,另一种是Lock;这两种的基本原理在之前的文章中已经做了分析:

    深入理解Synchronized实现原理
    java AQS的实现原理

    这次我们从最常用的Lock也就是ReentrantLock的Acquire和Release锁的过程入手,一步一步的跟源代码,探究获取锁和释放锁的步骤。

    获取锁

    ReentrantLock lock=new ReentrantLock();
    lock.lock();
    
    1.lock()

    上面代码是我们常用的获取锁的方式:调用ReentrantLock 的lock()方法,默认情况下ReentrantLock是一个非公平锁,类名NonfairSync,属于ReentrantLock的内部类,我们来看源码:

    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))
                    setExclusiveOwnerThread(Thread.currentThread());
                else
                    acquire(1);
            }
    
            protected final boolean tryAcquire(int acquires) {
                return nonfairTryAcquire(acquires);
            }
        }
    

    这里的lock方法先去通过CAS操作将state的值从0变为1,(注:ReentrantLock用state表示“持有锁的线程已经重复获取该锁的次数”。当state等于0时,表示当前没有线程持有锁),如果成功,就设置ExclusiveOwnerThread的值为当前线程(Exclusive是独占的意思,ReentrantLock用exclusiveOwnerThread表示“持有锁的线程”)。
    如果设置失败,说明state>0已经有线程持有了锁,此时执行acquire(1)再请求一次锁。

    2.acquire()
    /**
         * Acquires in exclusive mode, ignoring interrupts.  Implemented
         * by invoking at least once {@link #tryAcquire},
         * returning on success.  Otherwise the thread is queued, possibly
         * repeatedly blocking and unblocking, invoking {@link
         * #tryAcquire} until success.  This method can be used
         * to implement method {@link Lock#lock}.
         *
         * @param arg the acquire argument.  This value is conveyed to
         *        {@link #tryAcquire} but is otherwise uninterpreted and
         *        can represent anything you like.
         */
        public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }
    

    这里我们注意一下acquire方法上面的注释,已经说得很清楚了,这里我大概说下我的理解:

    请求独占锁,忽略所有中断,至少执行一次tryAcquire,如果成功就返回,否则线程进入阻塞--唤醒两种状态切换中,直到tryAcquire成功。

    我们对里面的tryAcquire(),、addWaiter()、acquireQueued()挨个分析。
    在这个方法里先执行tryAcquire(arg):

    3.tryAcquire()
    final boolean nonfairTryAcquire(int acquires) {
                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;
                    if (nextc < 0) // overflow
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }
    

    先判断state是否为0,如果为0就执行上面提到的lock方法的前半部分,通过CAS操作将state的值从0变为1,否则判断当前线程是否为exclusiveOwnerThread,然后把state++,也就是重入锁的体现,我们注意前半部分是通过CAS来保证同步,后半部分并没有同步的体现,原因是:

    后半部分是线程重入,再次获得锁时才触发的操作,此时当前线程拥有锁,所以对ReentrantLock的属性操作是无需加锁的。

    如果tryAcquire()获取失败,则要执行addWaiter()向等待队列中添加一个独占模式的节点

    4.addWaiter()
    /**
         * Creates and enqueues node for current thread and given mode.
         *
         * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
         * @return the new node
         */
        private Node addWaiter(Node mode) {
            Node node = new Node(Thread.currentThread(), mode);
            // Try the fast path of enq; backup to full enq on failure
            Node pred = tail;
            if (pred != null) {
                node.prev = pred;
                if (compareAndSetTail(pred, node)) {
                    pred.next = node;
                    return node;
                }
            }
            enq(node);
            return node;
        }
    

    这个方法的注释:创建一个入队node为当前线程,Node.EXCLUSIVE 是独占锁, Node.SHARED 是共享锁。
    先找到等待队列的tail节点pred,如果pred!=null,就把当前线程添加到pred后面进入等待队列,如果不存在tail节点执行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;
                    }
                }
            }
        }
    

    这里进行了循环,如果此时存在了tail就执行同上一步骤的添加队尾操作,如果依然不存在,就把当前线程作为head结点。
    插入节点后,调用acquireQueued()进行阻塞

    5.acquireQueued()
    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);
            }
        }
    

    先获取当前节点的前一节点p,如果p是head的话就再进行一次tryAcquire(arg)操作,如果成功就返回,否则就执行shouldParkAfterFailedAcquire、parkAndCheckInterrupt来达到阻塞效果;

    6.shouldParkAfterFailedAcquire()
    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
                 * 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;
        }
    

    addWaiter()构造的新节点,waitStatus的默认值是0。此时,进入最后一个if判断,CAS设置pred.waitStatus为SIGNAL==-1。最后返回false。

    回到第五步acquireQueued()中后,由于shouldParkAfterFailedAcquire()返回false,会继续进行循环。假设node的前继节点pred仍然不是头结点或锁获取失败,则会再次进入shouldParkAfterFailedAcquire()。上一轮循环中,已经将pred.waitStatus设置为SIGNAL==-1,则这次会进入第一个判断条件,直接返回true,表示应该阻塞。

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

    很显然,一旦shouldParkAfterFailedAcquire返回true也就是应该阻塞,就会执行parkAndCheckInterrupt()来达到阻塞效果,此时线程阻塞在这里,需要其它线程来唤醒,唤醒后就会再次循环第5步acquireQueued里的请求逻辑。

    我们回到第6步,看一个留下的逻辑片段

    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;
            } 
    

    什么时候会遇到ws > 0的case呢?当pred所维护的获取请求被取消时(也就是node的waitStatus 值为CANCELLED),这时就会循环移除所有被取消的前继节点pred,直到找到未被取消的pred。移除所有被取消的前继节点后,直接返回false。

    8.cancelAcquire()

    到这里我们回到第5步可以看到主体逻辑基本走完了,在该方法的finally里有一个cancelAcquire()方法

    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.
            Node predNext = pred.next;
    
            // 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.
            if (node == tail && compareAndSetTail(node, pred)) {
                compareAndSetNext(pred, predNext, null);
            } else {
                // If successor needs signal, try to set pred's next-link
                // so it will get one. Otherwise wake it up to propagate.
                int ws;
                if (pred != head &&
                    ((ws = pred.waitStatus) == Node.SIGNAL ||
                     (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                    pred.thread != null) {
                    Node next = node.next;
                    if (next != null && next.waitStatus <= 0)
                        compareAndSetNext(pred, predNext, next);
                } else {
                    unparkSuccessor(node);
                }
    
                node.next = node; // help GC
            }
        }
    

    也就是在第5步的执行过程中,如果出现异常或者出现中断,就会执行finally的取消线程的请求操作,核心代码是node.waitStatus = Node.CANCELLED;将线程的状态改为CANCELLED。

    释放锁

    1.release()
    /**
         * Releases in exclusive mode.  Implemented by unblocking one or
         * more threads if {@link #tryRelease} returns true.
         * This method can be used to implement method {@link Lock#unlock}.
         *
         * @param arg the release argument.  This value is conveyed to
         *        {@link #tryRelease} but is otherwise uninterpreted and
         *        can represent anything you like.
         * @return the value returned from {@link #tryRelease}
         */
        public final boolean release(int arg) {
            if (tryRelease(arg)) {
                Node h = head;
                if (h != null && h.waitStatus != 0)
                    unparkSuccessor(h);
                return true;
            }
            return false;
        }
    

    我们还是通过方法上面的注释来理解一下:
    释放独占锁,如果tryRelease成功返回true的话就会解开阻塞等待的线程
    显然,tryRelease方法来释放锁,如果释放成功,先判断head节点是否有效,最后unparkSuccessor启动后续等待的线程。

    2.tryRelease()
    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;
                    setExclusiveOwnerThread(null);
                }
                setState(c);
                return free;
            }
    

    先获取state减去释放的一次,然后判断当前线程是否和持有锁线程一致,如果不一致,抛出异常,继续判断state的值,只有当值为0时,free标志才置为true,否则说明是重入锁,需要多次释放直到state为0。

    3.unparkSuccessor()
    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);
        }
    

    这个方法名:启动后续线程,先拿到head节点的waitStatus并清空,然后获取next节点,并做检查,如果next节点失效,就从等待队列的尾部进行轮询,拿到第一个有效的节点,然后通过LockSupport.unpark(s.thread);唤醒,令该线程重新进入到获取锁的第5步循环去acquire锁。

    疑惑点

    1.我们在获取锁的很多步骤中看到tryAcquire的操作,原因是当获取一次失败后,程序会去执行失败后的逻辑代码,但是在执行过程中有可能锁的状态也同时发生了变化(释放锁、pred节点失效等情况),这时候需要去tryAcquire一下,省去了阻塞再唤醒的成本。
    2.等待队列的waitStatus属性使用很多,在这里我们先读一下源码注释:

    /** waitStatus value to indicate thread has cancelled */
            static final int CANCELLED =  1;
            /** waitStatus value to indicate successor's thread needs unparking */
            static final int SIGNAL    = -1;
            /** waitStatus value to indicate thread is waiting on condition */
            static final int CONDITION = -2;
            /**
             * waitStatus value to indicate the next acquireShared should
             * unconditionally propagate
             */
            static final int PROPAGATE = -3;
    
            /**
             * Status field, taking on only the values:
             *   SIGNAL:     The successor of this node is (or will soon be)
             *               blocked (via park), so the current node must
             *               unpark its successor when it releases or
             *               cancels. To avoid races, acquire methods must
             *               first indicate they need a signal,
             *               then retry the atomic acquire, and then,
             *               on failure, block.
             *   CANCELLED:  This node is cancelled due to timeout or interrupt.
             *               Nodes never leave this state. In particular,
             *               a thread with cancelled node never again blocks.
             *   CONDITION:  This node is currently on a condition queue.
             *               It will not be used as a sync queue node
             *               until transferred, at which time the status
             *               will be set to 0. (Use of this value here has
             *               nothing to do with the other uses of the
             *               field, but simplifies mechanics.)
             *   PROPAGATE:  A releaseShared should be propagated to other
             *               nodes. This is set (for head node only) in
             *               doReleaseShared to ensure propagation
             *               continues, even if other operations have
             *               since intervened.
             *   0:          None of the above
             *
             * The values are arranged numerically to simplify use.
             * Non-negative values mean that a node doesn't need to
             * signal. So, most code doesn't need to check for particular
             * values, just for sign.
             *
             * The field is initialized to 0 for normal sync nodes, and
             * CONDITION for condition nodes.  It is modified using CAS
             * (or when possible, unconditional volatile writes).
             */
            volatile int waitStatus;
    

    SIGNAL:后续线程正在阻塞,所以当前node在释放锁时必须启动后续线程,为了避免竞争激烈,acquire 方法第一次执行需要一个信号,也就是这个启动信号。
    CANCELLED:这个node失效了,因为超时或者被中断等原因。
    CONDITION:这个node当前是属于条件锁
    PROPAGATE:这个node是共享锁节点,他需要进行唤醒传播

    相关文章

      网友评论

        本文标题:一步步透彻理解Lock的Acquire和Release原理源码

        本文链接:https://www.haomeiwen.com/subject/ujgwnxtx.html