ReentrantLock分为公平锁和非公平锁,那底层分别是如何实现的呢

185 阅读3分钟

在 Java 里,ReentrantLock借助 AQS(AbstractQueuedSynchronizer)来实现公平锁和非公平锁,不过它们的实现方式存在差异。下面为你详细介绍这两种锁的底层实现原理。

非公平锁的实现原理

非公平锁在获取锁时,会先尝试通过 CAS 操作来抢占锁资源。若抢占成功,就直接获取到锁;只有当抢占失败时,才会进入 AQS 队列等待。其具体实现步骤如下:

  1. 尝试抢占锁:当线程调用lock()方法时,非公平锁会马上尝试利用 CAS 操作将 state 从 0 设置为 1。若设置成功,就表明线程获取到了锁。

  2. 锁重入处理:要是当前线程已经持有了该锁,再次获取锁时,只需把 state 的值加 1,以此记录锁的重入次数。

  3. 进入等待队列:若 CAS 操作失败,说明锁已被其他线程持有,此时线程会被封装成 Node 节点,加入到 AQS 的 CLH 队列尾部,进入等待状态。

下面是简化后的代码实现:


final void lock() {
    if (compareAndSetState(0, 1)) // 第一步:直接尝试CAS抢占锁
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1); // 调用AQS的acquire方法
}

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) { // 再次尝试CAS
            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 队列的顺序来获取锁。具体实现步骤如下:

  1. 检查队列状态:当线程调用lock()方法时,公平锁会先查看 AQS 队列中是否有其他线程在等待。若队列不为空,当前线程就会直接进入队列尾部等待。

  2. 尝试获取锁:只有当队列中没有其他等待线程,或者当前线程是队列的头节点时,才会尝试通过 CAS 操作获取锁。

  3. 锁重入处理:和非公平锁一样,公平锁也需要处理锁的重入情况。

下面是简化后的代码实现:

final void lock() {
    acquire(1); // 直接调用AQS的acquire方法
}

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 关键区别:先检查队列中是否有前驱节点,再尝试CAS
        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;
}

关键差异总结

对比项公平锁非公平锁
获取锁的顺序严格按照请求顺序,依赖 AQS 队列不保证顺序,可能后请求的线程先获取到锁
性能由于要频繁检查队列状态,性能相对较低CAS 操作的成功率较高,性能相对较好
饥饿问题不会出现线程饥饿情况可能会导致部分线程长时间无法获取到锁
实现复杂度较高,需要维护队列的顺序较低,无需额外处理队列顺序

综上所述,公平锁保证了锁获取的公平性,但这是以牺牲一定的性能为代价的;而非公平锁虽然可能会造成线程饥饿,不过在大多数情况下,它的性能表现更优。