Lihang Liu's Homepage

深入理解 ReentrantLock

本文是对 ReentrantLock 源码的分析以及总结,详细介绍了其是实现公平与非公平锁的加解锁的原理、其与 AbstractQueuedSynchronizer 的关联。

简介

可重入锁是 JUC 包中提供的一种可重入的互斥锁,其实现基于 [[AbstractQueuedSynchronizer]] , Java 源码的注释很好的说明了 ReentrantLock 是什么: ReentrantLock 和 Java synchronized 的语义一致,但包含更多的功能。

A reentrant mutual exclusion Lock with the same basic behavior and semantics as the implicit monitor lock accessed using synchronized methods and statements, but with extended capabilities.

代码结构

AQS 和其实现类之间的关系属于模板方法:AQS 提供基础的算法框架,实现类实现抽象方法来实现自己独特的功能和语义。 AbstractQueuedSynchronizer 的源码注释中有这么一段话,很好地说明了 AQS 与 JUC 包中具体同步器之间的关系:

Subclasses should be defined as non-public internal helper classes that are used to implement the synchronization properties of their enclosing class. Class AbstractQueuedSynchronizer does not implement any synchronization interface. Instead it defines methods such as acquireInterruptibly that can be invoked as appropriate by concrete locks and related synchronizers to implement their public methods.

有个这样一个前提,我们才能更好地理解 RenentrantLock 的实现和原理。

ReentrantLock 的整体结构如下:

  • ReentrantLock 实现了 Lock 接口,使用者仅需调用 lockunlock 接口即可实现加解锁。
  • 抽象内部类 Sync 继承了 AbstractQueuedSynchronizerSync 类实现了不分 AQS 中定义的模板方法,比如 tryReleaseisHeldExclusively
  • 为了支持公平锁和非公平锁两种语义, RenentrantLock 内部定义了 FairSyncNonfairSync 两个继承自 Sync 的类, FaireSyncNonfairSync 实现 AQS 中定义的 tryAcquire 模板方法,实现了公平锁及非公平锁的加锁逻辑。

public class ReentrantLock implements Lock, java.io.Serializable {

    private final Sync sync;  // 同步器变量

    // Sync 继承自 AQS,实现了 AQS 中定义的模板方法
    abstract static class Sync extends AbstractQueuedSynchronizer {...}


    // 非公平锁实现类
    static final class NonfairSync extends Sync {...}

    // 公平锁实现类
    static final class FairSync extends Sync {...}
    
}

源码分析

构造函数

ReentrantLock 在构造函数时可以传入一个不二参数,来生成一个公平锁实例,默认情况下的 ReentrantLock 是非公平锁:


public class ReentrantLock implements Lock, java.io.Serializable {

    /**
     * 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();
    }
}

非公平锁加锁实现

非公平锁的加锁过程如下:

  1. 调用 NonfairSync 实现的 lock 方法,该方法将调用 AQS 中的 compareAndSetState ,看能否直接获取锁,如果该方法返回 true 那么线程将设置当前线程为 ReentrantLock 的独占持有者;
  2. 如果 AQS state 状态变量不为 0,则调用 AQS 中的 acquire 方法;
  3. AQS 的 acquire 会调用实现类的 tryAcquire 方法,也就是 NonfairSync 自己实现的 tryAcquire 方法,该方法会调用 Sync 中定义的 nonfairTryAcquire
  4. Sync 中的 nonfairTryAcquire 将尝试原子性地更新 AQS 中的 state 状态变量,这里有两种情况:
    1. 当前线程是 ReentrantLock 的持有者,那么只需更新 AQS 的 state 状态变量即可,并返回 true 表示成功获取锁;
    2. 如果当前线程不是可重入锁的持有者,则判断当前是否可以获取锁,并尝试原子性更新 AQS 的 state 状态变量,成功则返回 true ,否则返回 false
  5. 失败后将返回到 AQS 的 acquire 中,将当前线程加入到 AQS 的等待队列里。

public class ReentrantLock implements Lock, java.io.Serializable {

    abstract static class Sync extends AbstractQueuedSynchronizer {

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

    }


    // 非公平锁实现类
    static final class NonfairSync extends Sync {

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

    }
}

公平锁加锁实现

公平锁加锁过程如下:

  1. 调用 FairSynclock 方法,其会调用 AQS 中的 acquire 方法;
  2. AQS acquire 方法调用具体实现类,也就是 FairSync 自己的 tryAcquire 方法尝试获取锁;
  3. tryAcuire 首先会判断能否获取锁,也就是判断 AQS state 变量的值:
    1. 如果为 0,则判断 AQS 的等待队列中是否有已经在排队的线程,有的话则返回 false ,回到 AQS 的 acquire 中进行后续入队操作。
    2. 如果不为 0,那么判断当前所是否是 ReentrantLock 的持有者线程,是则原子性更新 AQS 的 state 变量,否则返回 false ,回到 AQS 的 acquire 中进行后续入队操作。

public class ReentrantLock implements Lock, java.io.Serializable {

    static final class FairSync extends Sync {

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

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

}

解锁实现

无论是公平锁还是非公平锁实现,解锁代码逻辑是一样的,因此是由 Sync 类来实现的:

  1. 计算解锁后 state 的变量值 c
  2. 防御性判断当前线程是否为 ReentrantLock 的持有者线程,如果不是那么就有问题了,抛异常终止
  3. 判断 c 是否为 0,如果是则原子性地设置可重入锁的持有者线程为 null
  4. 原子性更新 AQS 的 state
  5. 返回锁是否被释放
public class ReentrantLock implements Lock, java.io.Serializable {

    abstract static class Sync extends AbstractQueuedSynchronizer {

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

    }
}

可重入性

从源码可以看出, ReentrantLock 的可重入性,来源于加锁时对 AQS state 变量值的判断:如果 state 值不为 0,则判断当前线程是否为锁的持有线程,如果是,则直接原子性的将 state 的值加一。

解锁时, unlock 方法调用 Sync 内的 tryRelease 方法,原子性的将 AQS 中的 state 值减一,如果 state 变为 0,则将 ReentrantLock 的持有者线程设为 null

这也就要求 ReentrantLock 的加锁和解锁必须是成对出现和匹配的,否则,因为少调用了 tryRelease 方法,AQS 的 state 不为 0,将导致排队的线程永远也无法获取锁。正如源码注释说的那样, ReentrantLock 的最佳实践:

It is recommended practice to always immediately follow a call to lock with a try block, most typically in a before/after construction such as:

 class X {
    private final ReentrantLock lock = new ReentrantLock ();
    // ...

    public void m () {
        lock. lock ();  // block until condition holds
        try {
            // ... method body
        } finally {
            lock. unlock ()
        }
    }
}

Reference

  1. Juc 源码