乐趣区

关于aqs:ReentrantLock-源码分析

ReentrantLock 源码剖析

阐明

  1. 本文基于 jdk 8 写作。
  2. @author JellyfishMIX – github / blog.jellyfishmix.com
  3. LICENSE GPL-2.0

锁机制的外围: Sync(锁)

    /**
     * Synchronizer providing all implementation mechanics
     * 提供所有实现机制的同步器,这是 ReentrantLock 能实现锁机制的外围
     */
    private final Sync sync;

    /**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer

ReentrantLock 的 lock 与 unlock 办法,是通过 Sync 实现的,Sync 是一个 ReentrantLock 的外部类,继承了 AbstractQueuedSynchronizer(简称 AQS)。Sync 是 ReentrantLock 能实现锁机制的外围。

AQS 是一个 java 语言层面的同步器开发框架,对于 AbstractQueuedSynchronizer 的源码剖析日后欠缺,这里先把 AbstractQueuedSynchronizer 当作黑盒解决,只应用它的 api。

Sync 的 state 属性(继承自父类)

Sync 的 state 属性继承自父类 AbstractQueuedSynchronizer,AbstractQueuedSynchronizer 中 state 的定义:

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

对于 Sync,能够了解为这是一个锁的 state 数量。在独占锁中,加锁的时候 state 会 +1(当然能够本人批改这个值),在解锁的时候 -1。同一个锁,在持有锁的线程重入后,state 可能会被叠加为 2, 3, 4… 等等。只有 unlock() 的次数与 lock() 的次数对应,才会将持有锁的线程设置为空,只有这种状况下解锁时的 tryRelease 办法才会返回 true。

偏心锁和非偏心锁 (FairSync, NonfairSync)

偏心锁:每个线程抢占锁的程序为先后调用 lock 办法的程序顺次获取锁,相似于排队吃饭。

非偏心锁:每个线程抢占锁的程序不定,谁运气好,谁就获取到锁,和调用 lock 办法的先后顺序无关。

    /**
     * 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.
     * 传入 true 为偏心锁,传入 false 为非偏心锁
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}

偏心锁与非偏心锁是 Sync 的两个实现:NonfairSync 和 FairSync

    /**
     * Sync object for non-fair locks
     * 非偏心锁:每个线程抢占锁的程序不定,谁运气好,谁就获取到锁,和调用 lock 办法的先后顺序无关
     */
    static final class NonfairSync extends Sync
        
    /**
     * Sync object for fair locks
     * 偏心锁:每个线程抢占锁的程序为先后调用 lock 办法的程序顺次获取锁,相似于排队吃饭。*/
    static final class FairSync extends Sync

ReentrantLock 的 lock 办法

    /**
     * Acquires the lock.
     * 此办法是 ReentrantLock 的成员办法
     *
     * <p>Acquires the lock if it is not held by another thread and returns
     * immediately, setting the lock hold count to one.
     *
     * <p>If the current thread already holds the lock then the hold
     * count is incremented by one and the method returns immediately.
     *
     * <p>If the lock is held by another thread then the
     * current thread becomes disabled for thread scheduling
     * purposes and lies dormant until the lock has been acquired,
     * at which time the lock hold count is set to one.
     */
    public void lock() {
        // 调用 sync 的 lock 办法加锁
        sync.lock();}

ReentrantLock 中的 lock 办法间接调用了 sync 的 lock 办法加锁。

lock 办法流程图:

Sync 中的 lock 办法

        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         * 
         * Sync 的成员办法。由子类实现 lock 办法。*/
        abstract void lock();

Sync 的 lock 办法由子类实现,子类有两个:NonfairSync 非偏心锁,和 FairSync 偏心锁。偏心与非偏心的体现,就体现在对 lock 办法的实现上。也就是说,这里 sync 是偏心锁还是非偏心锁,是在结构 ReentrantLock 的时候,通过构造方法指定的。

NonfairSync 的 lock 办法(非偏心锁)

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         *
         * 加锁。此办法是 NonfairSync 的成员办法。*/
        final void lock() {
            // 先尝试进行一次 CAS 原子操作加锁
            if (compareAndSetState(0, 1))
                // 加锁胜利,设置持有独占锁的线程为以后线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 模板办法,封装了线程获取锁失败后,进入同步队列并阻塞的逻辑
                acquire(1);
        }

lock 办法先调用了一次 CAS 原子操作加锁。CAS(Compare-And-Swap),它是一条 cpu 原语,用于判断内存中某个地位的值是否为预期值,如果是则更改为新的值,这个过程是原子的。因为是 cpu 原语,所以人造不会有并发竟态危险。在 lock 办法中,它尝试从 0 设置为 1,示意尝试加锁。能设置胜利,则阐明拿到了锁。因为如果多个线程同时进行 CAS 操作,只有一个线程能胜利从 0 设置成 1,这也是从 cpu 层面保障的。

如果 CAS 加锁失败,则会调用 acquire 办法,acquire 办法继承自 AbstractQueuedSynchronizer。

总结一下逻辑:

if (CAS 操作加锁) {// 加锁胜利,设置持有独占锁的线程为以后线程} else {// 加锁失败,调用 AQS 的 acquire() 办法。封装了线程获取锁失败后,进入同步队列并休眠的逻辑。}
AbstractQueuedSynchronizer 的 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) {
        // tryAcquire 办法由子类实现
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();}

AbstractQueuedSynchronizer 的 acquire 办法是一个模板办法。模板办法的定义:定义一个操作中的算法骨架,而将算法的一些步骤提早到子类中,使得子类能够不扭转该算法构造的状况下重定义该算法的某些特定步骤。它是一品种行为型模式。

咱们先不须要关怀 acquire 模板办法的其余逻辑,站在本文配角 ReentrantLock 的角度,只须要关注本人要实现的 tryAcquire 办法即可。AbstractQueuedSynchronizer 中对 tryAcquire 办法的申明如下:

    /**
     * Attempts to acquire in exclusive mode. This method should query
     * if the state of the object permits it to be acquired in the
     * exclusive mode, and if so to acquire it.
     *
     * 尝试在独占模式下获取锁时,是否容许获取锁,由具体子类实现
     *
     * <p>This method is always invoked by the thread performing
     * acquire.  If this method reports failure, the acquire method
     * may queue the thread, if it is not already queued, until it is
     * signalled by a release from some other thread. This can be used
     * to implement method {@link Lock#tryLock()}.
     *
     * <p>The default
     * implementation throws {@link UnsupportedOperationException}.
     *
     * @param arg the acquire argument. This value is always the one
     *        passed to an acquire method, or is the value saved on entry
     *        to a condition wait.  The value is otherwise uninterpreted
     *        and can represent anything you like.
     * @return {@code true} if successful. Upon success, this object has
     *         been acquired.
     * @throws IllegalMonitorStateException if acquiring would place this
     *         synchronizer in an illegal state. This exception must be
     *         thrown in a consistent fashion for synchronization to work
     *         correctly.
     * @throws UnsupportedOperationException if exclusive mode is not supported
     */
    protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();
    }

在 ReentrantLock 中,外部类 NonfairSync 实现了 tryAcquire 办法。

NonfairSync 的 tryAcquire 办法
        protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);
        }

NonfairSync 调用了父类 Sync 的 nonfairTryAcquire 办法。

Sync 的 nonfairTryAcquire 办法
        /**
         * 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();
            // 以后锁的 state
            int c = getState();
            // 如果以后锁的 state 为 0,即锁闲暇
            if (c == 0) {
                // CAS 原子操作加锁,尝试把 0 设置成指定值
                if (compareAndSetState(0, acquires)) {
                    // 加锁胜利,设置持有锁的线程为以后线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 如果以后锁的 state 不为 0,然而以后线程是持有锁的线程,则容许重入锁,这里也体现了 ReentrantLock 是可重入锁
            else if (current == getExclusiveOwnerThread()) {
                // 重入次数
                int nextc = c + acquires;
                // 如果重入次数 < 0(重入次数溢出数据类型范畴)if (nextc < 0) // overflow
                    // 抛出 Error,提醒超出了锁的最大计数
                    throw new Error("Maximum lock count exceeded");
                // 设置锁的 state
                setState(nextc);
                // 胜利拿到锁
                return true;
            }
            // 如果如上逻辑都不满足,则没有拿到锁
            return false;
        }

简略来讲,nonfairTryAcquire 办法通过判断 state 是否为 0,来判断是否能够获取同步状态。如果 state 不为 0 则不能够获取同步状态。当然有一个例外,如果尝试获取同步状态的线程,就是曾经持有锁的线程,则能够胜利获取同步状态,同时 state + acquires。这里也体现了 ReentrantLock 是可重入锁。

总结一下逻辑:

if (判断 state 是否为 0) {
    // state 为 0 则能够加锁
    if (CAS 操作加锁) {
        // 加锁胜利
        return true;
    }
}
// state 不为 0 或 CAS 操作加锁失败
else if (判断以后线程是否是锁的持有线程) {
    // 如果以后线程是锁的持有线程,则还能够从新获取锁胜利,只须要给 state 的值再 + 1
    return true;
}
// 上述判断都不满足,阐明没有拿到锁
return false;

FairSync 的 lock 办法(偏心锁)

        /**
         * 此办法是 FairSync 的成员办法
         */
        final void lock() {
            // 模板办法,封装了线程获取锁失败后,进入同步队列并阻塞的逻辑
            acquire(1);
        }

偏心锁的 lock 逻辑很简略,acquire(1); 在非偏心锁中曾经解释过了。继承自 AbstractQueuedSynchronizer 的 acquire 办法,后面曾经剖析过。

须要留神的是,acquire 作为模版办法调用的 tryAcquire 办法,对于 FairSync,这个 tryAcquire 办法与 NonfairSync 是不同的。次要是在 if 调用 CAS 原子操作加锁时,FairSync 多了一步 hasQueuedPredecessors,判断是否有任何线程期待获取的工夫长于以后线程,如果没有比以后线程等待时间更长的,再去 CAS 加锁,这也是偏心锁的体现。

FairSync 的 tryAcquire 办法
        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         *
         * 此办法是 FairSync 的成员办法
         */
        protected final boolean tryAcquire(int acquires) {
            // 获取以后线程
            final Thread current = Thread.currentThread();
            // 以后锁的 state
            int c = getState();
            // 如果以后锁的 state 为 0,即锁闲暇
            if (c == 0) {
                // 判断是否有任何线程期待获取的工夫长于以后线程
                if (!hasQueuedPredecessors() &&
                        // CAS 原子操作加锁,尝试把 0 设置成指定值
                    compareAndSetState(0, acquires)) {
                    // 加锁胜利,设置持有锁的线程为以后线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 如果以后锁的 state 不为 0,然而以后线程是持有锁的线程,则容许重入锁,这里也体现了 ReentrantLock 是可重入锁
            else if (current == getExclusiveOwnerThread()) {
                // 重入次数
                int nextc = c + acquires;
                // 如果重入次数 < 0(重入次数溢出数据类型范畴)if (nextc < 0)
                    // 抛出 Error,提醒超出了锁的最大计数
                    throw new Error("Maximum lock count exceeded");
                // 设置锁的 state
                setState(nextc);
                // 胜利拿到锁
                return true;
            }
            // 如果如上逻辑都不满足,则没有拿到锁
            return false;
        }

简略来讲,FairSync 的 tryAcquire 办法通过判断 state 是否为 0,来判断是否能够获取同步状态。如果 state 不为 0 则不能够获取同步状态。当然有一个例外,如果尝试获取同步状态的线程,就是曾经持有锁的线程,则能够胜利获取同步状态,同时 state + acquires。这里也体现了 ReentrantLock 是可重入锁。

总结一下逻辑:

if (判断 state 是否为 0) {
    // state 为 0 则能够加锁
    if (! 判断是否有任何线程期待获取同步状态的工夫长于以后线程 && CAS 操作加锁) {
        // 加锁胜利
        return true;
    }
}
// state 不为 0 或 CAS 操作加锁失败
else if (判断以后线程是否是锁的持有线程) {
    // 如果以后线程是锁的持有线程,则还能够从新获取锁胜利,只须要给 state 的值再 + 1
    return true;
}
// 上述判断都不满足,阐明没有拿到锁
return false;

FairSync 偏心锁与 NonfairSync 非偏心锁 lock 办法的区别

上面比照一下非偏心锁与偏心锁 lock 办法实现的区别,lock 办法也是偏心锁与非偏心锁的全副区别。

        /**
         * NonfairSync 非偏心锁的 lock 办法
         */
        final void lock() {
            // 先尝试进行一次 CAS 原子操作加锁
            if (compareAndSetState(0, 1))
                // 加锁胜利,设置持有独占锁的线程为以后线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 模板办法,封装了线程获取锁失败后,进入同步队列并阻塞的逻辑
                acquire(1);
        }

        /**
         * FairSync 偏心锁的 lock 办法
         */
        final void lock() {
            // 模板办法,封装了线程获取锁失败后,进入同步队列并阻塞的逻辑
            acquire(1);
        }

能够看到,NonfairSync 在 lock 时,比 FairSync 多了一步,先尝试进行一次 CAS 原子操作加锁,这就体现出了不偏心。以后线程间接就加入了抢锁,而不是依照线程间调用 lock 的程序,先来后到进行抢锁。

lock 办法调用了父类 AbstractQueuedSynchronizer 的模版办法 acquire,acquire 作为模版办法调用的 tryAcquire 办法。对于 FairSync,这个 tryAcquire 办法与 NonfairSync 是不同的。

                // NonfairSync
                // CAS 原子操作加锁,尝试把 0 设置成指定值
                if (compareAndSetState(0, acquires)) {
                    // 加锁胜利,设置持有锁的线程为以后线程
                    setExclusiveOwnerThread(current);
                    return true;
                }

                // FairSync
                // 判断是否有任何线程期待获取的工夫长于以后线程
                if (!hasQueuedPredecessors() &&
                        // CAS 原子操作加锁,尝试把 0 设置成指定值
                    compareAndSetState(0, acquires)) {
                    // 加锁胜利,设置持有锁的线程为以后线程
                    setExclusiveOwnerThread(current);
                    return true;
                }

不同点上文也介绍了,再反复一下,次要是在 if 调用 CAS 原子操作加锁时,FairSync 多了一步 hasQueuedPredecessors,判断是否有任何线程期待获取的工夫长于以后线程,如果没有比以后线程等待时间更长的,再去 CAS 加锁,这也是偏心锁的体现。

ReentrantLock 的 unlock 办法

解锁办法,如下:

    /**
     * Attempts to release this lock.
     * 解锁。此办法是 ReentrantLock 的成员办法
     *
     * <p>If the current thread is the holder of this lock then the hold
     * count is decremented.  If the hold count is now zero then the lock
     * is released.  If the current thread is not the holder of this
     * lock then {@link IllegalMonitorStateException} is thrown.
     *
     * @throws IllegalMonitorStateException if the current thread does not
     *         hold this lock
     */
    public void unlock() {
        // 调用 sync 的 release 办法
        sync.release(1);
    }

sync 的 release 办法继承自 AbstractQueuedSynchronizer。

AbstractQueuedSynchronizer 的 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}.
     *
     * 模版办法,其中 tryRelease 办法由子类实现。以后锁开释指定的 state 数量,并返回锁是否开释胜利。*
     * @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) {
        // tryRelease 办法由子类实现
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

AbstractQueuedSynchronizer 的 release 办法是一个模板办法。模板办法的定义:定义一个操作中的算法骨架,而将算法的一些步骤提早到子类中,使得子类能够不扭转该算法构造的状况下重定义该算法的某些特定步骤。它是一品种行为型模式。

咱们先不须要关怀 release 模板办法的其余逻辑,站在本文配角 ReentrantLock 的角度,只须要关注本人要实现的 tryRelease 办法即可。AbstractQueuedSynchronizer 中对 tryRelease 办法的申明如下:

    /**
     * Attempts to set the state to reflect a release in exclusive
     * mode.
     * 
     * 尝试设置同步器的 state 值,来反映一次在独占模式中的 release(开释) 操作。是否容许开释锁,由具体子类实现。*
     * <p>This method is always invoked by the thread performing release.
     *
     * <p>The default implementation throws
     * {@link UnsupportedOperationException}.
     *
     * @param arg the release argument. This value is always the one
     *        passed to a release method, or the current state value upon
     *        entry to a condition wait.  The value is otherwise
     *        uninterpreted and can represent anything you like.
     * @return {@code true} if this object is now in a fully released
     *         state, so that any waiting threads may attempt to acquire;
     *         and {@code false} otherwise.
     * @throws IllegalMonitorStateException if releasing would place this
     *         synchronizer in an illegal state. This exception must be
     *         thrown in a consistent fashion for synchronization to work
     *         correctly.
     * @throws UnsupportedOperationException if exclusive mode is not supported
     */
    protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();
    }

在 ReentrantLock 中,外部类 Sync 实现了 tryRelease 办法。

Sync 的 tryRelease 办法

        /**
         * 能够认为是一个设置锁状态的操作,通过将锁以后的 state 数量,减掉传入的要开释的 state 数量 (比方参数能够是 1)
         * 如果后果 state 为 0,就将持有独占锁的线程设置为 null,即开释锁,以使其它的线程有机会拿锁。* 此办法是 Sync 的成员办法
         *
         * @param releases 要开释的 state 数量
         * @return
         */
        protected final boolean tryRelease(int releases) {// 后果 state = ( 锁以后的 state 数量) - (要开释的 state 数量)
            int c = getState() - releases;
            // 如果以后线程不是持有锁的线程,抛出异样
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            // 示意锁是否被开释
            boolean free = false;
            // 如果后果 state 为 0,阐明持有锁的线程曾经齐全开释
            if (c == 0) {
                free = true;
                // 设置持有独占锁的线程为 null,即开释锁
                setExclusiveOwnerThread(null);
            }
            // 设置锁的 state 为计算后后果
            setState(c);
            return free;
        }

假如办法入参 int releases 为 1。能够简略了解为,给同步状态 state – 1。-1 计算实现后,如果 state 为 0,就判断为胜利开释同步状态。

退出移动版