MST

星途 面试题库

面试题:Java中ReentrantLock的公平锁与非公平锁在实现和性能上的差异

在Java的ReentrantLock中,公平锁和非公平锁是如何实现的?它们在性能和应用场景上各有什么特点?
17.3万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

公平锁实现

  1. 公平锁在ReentrantLock中的实现基于FairSync
    • FairSync继承自Sync类(SyncReentrantLock的内部抽象类,继承自AbstractQueuedSynchronizer,即AQS)。
    • FairSynctryAcquire方法中,首先检查当前线程是否为队列中等待时间最长的线程。如果当前线程不是队列中等待时间最长的线程,它会直接返回false,即获取锁失败,线程会被放入AQS队列等待。只有当队列中没有其他等待线程或者当前线程是队列中等待时间最长的线程时,才尝试获取锁,成功获取锁后会更新同步状态。
    • 例如:
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
    final boolean nonfairTryAcquire(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;
    }
}

非公平锁实现

  1. 非公平锁在ReentrantLock中的实现基于NonfairSync
    • NonfairSync同样继承自Sync类。
    • NonfairSynctryAcquire方法中,线程会直接尝试获取锁,而不管队列中是否有等待时间更长的线程。它首先检查同步状态是否为0(表示锁未被占用),如果是,则直接尝试通过CAS(Compare - And - Swap)操作设置同步状态来获取锁。如果同步状态不为0且当前线程是持有锁的线程,则增加同步状态(实现可重入)。
    • 例如:
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    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;
    }
}

性能特点

  1. 公平锁性能
    • 优点:公平锁保证了等待时间最长的线程优先获取锁,在一些对公平性要求较高的场景下能保证所有线程都有机会执行。
    • 缺点:由于每次获取锁都要检查队列中是否有等待时间更长的线程,这种额外的检查操作会增加系统开销,尤其是在高并发场景下,频繁的线程切换和队列操作会导致性能下降。例如,在一个多线程竞争锁的场景中,公平锁可能会使得线程不断地在队列中等待和唤醒,造成上下文切换的开销增大。
  2. 非公平锁性能
    • 优点:非公平锁在高并发场景下性能通常优于公平锁。因为它不需要每次都检查队列状态,线程可以直接尝试获取锁,减少了线程上下文切换的开销。在锁竞争不是特别激烈的情况下,非公平锁可以使得线程快速获取锁,提高系统的吞吐量。
    • 缺点:非公平锁可能导致某些线程长时间等待获取不到锁,出现“饥饿”现象。例如,在一个高并发场景中,如果新的线程不断地快速获取锁,那么一些等待时间较长的线程可能会一直处于等待状态。

应用场景

  1. 公平锁应用场景
    • 资源分配场景:例如在一些任务调度系统中,每个任务都需要公平地获取执行资源,公平锁可以保证每个任务按照到达的顺序依次执行,避免某些任务长时间得不到执行机会。
    • 对公平性要求极高的场景:如银行排队系统,每个客户都期望按照先来后到的顺序得到服务,公平锁可以模拟这种公平的排队机制。
  2. 非公平锁应用场景
    • 高并发且对公平性要求不高的场景:如电商的抢购活动,在这种场景下更注重系统的吞吐量和响应速度,允许部分线程快速获取锁执行抢购逻辑,即使可能存在少量线程“饥饿”,也不影响整体业务的高效运行。
    • 大多数普通的多线程并发场景:因为非公平锁在大多数情况下能提供更好的性能,在没有特别要求公平性的情况下,使用非公平锁可以提高系统的运行效率。