大话系列 - ReentrantLock源码分析

  ReentrantLock也是Java面试中必不可少的一个环节,所以这里就分析一下其中的源码。

一、AQS

  AQS是JUC中非常重要的一个基类,其中提供了构建同步工具类提供了基础框架。那就先来看看这个基类中的一些重点内容。

static final class Node

  首先是Node内部类,我们知道在AQS的内部提供了一个双链链表组成的队列,这个Node就是链表的数据结构

        /** 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;

  Node类中几个比较重要的状态常量,重点说一下CANCELLED : 如果一个线程在等待锁的过程中取消了,那么对应Node元素的waitStatus就会变成这个值,表示其不再需要等待获取锁。SINGAL是表示一个标量,字面上理解就是信号的意思,也就是在锁被释放之后,发出信号的那个Node,正常情况下当前持有锁的线程对应Node的waitStatus为SINGAL。

    /**
     * The synchronization state.
     */
    private volatile int state;

  AQS中最重要的一个变量,根据不同也业务场景会表示不同的含义,比如当前的ReentrantLock就使用这个值表示锁是否可用以及重入的次数。那在Samphore中就用这个值表示许可的数量

    /**
     * Returns the current value of synchronization state.
     * This operation has memory semantics of a {@code volatile} read.
     * @return current state value
     */
    protected final int getState() {
        return state;
    }

    /**
     * Sets the value of synchronization state.
     * This operation has memory semantics of a {@code volatile} write.
     * @param newState the new state value
     */
    protected final void setState(int newState) {
        state = newState;
    }

    /**
     * Atomically sets synchronization state to the given updated
     * value if the current state value equals the expected value.
     * This operation has memory semantics of a {@code volatile} read
     * and write.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that the actual
     *         value was not equal to the expected value.
     */
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

  接下来就是这三个方法,就是围绕status字段进行修改。

  其他的内容也同样重要,在后面流程了解代码遇到的时候再去说明,这样效果会比较好。这里就先说明几个比较基本且重要的部分,因为在ReentrantLock中非公平锁使用的比较多,涵盖的源码范围也比较大,所以下面会以非公平锁为例子来说明ReentrantLock的原理。

二、非公平锁

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

   ReentrantLock有两个构造方法,首先第一个默认无参的是会创建一个非公平锁,如果有布尔类型的参数,那布尔为true的话,会创建一个公平锁,反之为非公平锁。那这里我们可以看到不管是哪个锁,其实都是继承自Sync的一个父类。

abstract static class Sync extends AbstractQueuedSynchronizer

  这里可以看到这个Sync就是AQS的一个子类,也就是他的服务能力是依靠AQS的

        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
        abstract void lock();

  提供了一个抽象方法,这个会在公平锁和非公平锁中分别提供实现,因为这里咱们重点说非公平锁,所以就来看一下非公平锁中的lock方法是怎么实现的

    /**
     * Sync object for non-fair locks
     */
    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);
        }
    }

  可以看到首先是采用CAS来尝试将state字段设置为1,也就是说这个state字段在ReentrantLock中0表示没有任何线程持有这个锁。那如果设置成功,就会把当前线程设置为独占所有,并且线程标记。

    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

  这样如果当前线程再次获取这个锁的时候,就可以通过线程标记来进行确定了,那如果CAS执行失败,说明当前已经有其他线程持有了这个锁,那么就会执行AQS中的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();
    }

  这里的tryAcquire方式是一个模板方法,需要子类去实现,那么就是对应FairSync和NofairSync两个子类中都会有对这个方法的实现,所以我们先看在 NonfairSync 中的tryAcquire,但是 NonfairSync 又委托给了Sync,也就是在父类中提供了默认的非公平锁的实现方式。 

        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        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;
        }

  那这里首先是获取当前线程,然后通过调用AQS中的getState来获取锁标识位的值,如果为0 ,那么就尝试通过CAS来获取锁。如果不为0那么久检查是否为重入锁。如果都不是的话,那就直接返回false,表示尝试获取锁失败

  再来看一下公平锁中的tryAcquire方法的实现:

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    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;
        }

  与非公平锁不同的是,这里如果当前state状态为0的时候,增加了一个判断,就是当前等待队列中没有其他线程在等待,也就是 hasQueuedPredecessors 方法。下面看一下这个方法的实现内容:

//TODO

  根据上面的if条件只有在tryAccquire获取失败才会继续执行 acquireQueued 方法,但是这个方法的入参是一个调用函数的返回值,所以这里先一下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对象,然后判断一个当前的tail节点是否为null,如果不为null表示当前的等待队列已经被创建好了,否则就执行enq方法

    /**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    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;
                }
            }
        }
    }

  这里可以看到一进入到方法就是一个死循环, 那这里首先看如果是等待队列还没有被初始化,第一次进去这个方法的时候会先进入到if分支中,通过CAS Node的方式创建一个空节点,然后会再次执行循环,那么这时if中的条件就不满足了,就会进入到else分支中,将当前的Node节点插入到队尾,所以说addWaiter方法就是把当前线程添加到等待队列中,返回的是当前Node对象。

    /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    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);
        }
    }

  这时再看 acquireQueued 方法直接就是一个死循环,首先获取当前节点的前置节点p,然后如果p是头节点(头节点可能就是现在获取锁的线程,因为第一个获取锁的对象肯定不在等待队列中),然后尝试一次获取锁,如果成功了那就把当前节点设置为头节点。否则就会进行到是否需要等待的判断流程中,因为这个方式只有在if 条件分支中才能返回。

    /**
     * Checks and updates status for a node that failed to acquire.
     * Returns true if thread should block. This is the main signal
     * control in all acquire loops.  Requires that pred == node.prev.
     *
     * @param pred node's predecessor holding status
     * @param node the node
     * @return {@code true} if thread should block
     */
    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;
    }

  这个方式是判断当前线程是否需要阻塞等待的,首先判断如果前置节点的状态要是SINGAL的话,那就说明锁正在被前置节点持有,那就直接等待即可,所以会返回true。那如果waitState大于0,也就是CANCELLED的状态,那就说明前置节点已经取消了等待,不会再获取锁了,所以这个时候就会循环的去获取再往前的节点,直到找到一个没有取消的节点,然后这个方法就返回了false,但是我们知道其外层调用是一个死循环,所以会一直循环到这里来判断,直到前置节点的状态为SINGAL为止。

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

  如果当前线程可以放心的去阻塞,那么就直接通过LockSupport 方法阻塞当前线程,等待唤醒,然后清除当前线程的中断状态。一旦当前线程被阻塞等待,那 interrupted 就被置为true了,其实就是表示当前线程已经进入到阻塞状态了,在可以获取锁的时候,可以被打断。

  还记得在acquire方法中,如果if分支条件成立,也就是说当前线程已经被前置节点唤醒,需要取消掉阻塞的状态,那么这里面调用了一个 selfInterrupt 方法 

    /**
     * Convenience method to interrupt current thread.
     */
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

  在线程进入到等待状态的时候,已经清除了自身的中断标志,那么这里直接打断唤醒就可以了

三、公平锁

  整体的逻辑与非公平锁相同,只是在获取锁的时候有点区别。

    /**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

  这里可以看到在有新的线程请求获取锁的时候,这个不是先判断是否可以通过CAS的方式获取到锁,而是直接执行acquire方法,将当前线程添加到等待队列中。所以这里也就是与非公平锁的主要区别,但是在非公平锁中,一旦被添加到队列中仍然是先等待的线程要比后等待的线程先获得到锁。

四、解锁

  这里解锁操作就不区分公平还是非公平了,直接调用的是Sync方法中的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;
    }

  这里就是尝试解锁操作

        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的值,这个值是有可能大于1的,因为存在重入的情况,那如果出现了重入,只有在把最后一个锁释放了,才能释放这个锁。同样如果执行解锁的线程并没有拥有这个锁,会通过异常的方式进行提示。

    /**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     */
    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);
    }

  这里如果解锁成功,如果等待队列中还有等待的线程,那么就需要对其唤醒。这里也是借助LockSupport来实现对指定线程的唤醒操作。

   

  

 

posted @ 2021-08-15 00:50  SyrupzZ  阅读(179)  评论(0)    收藏  举报