ReentrantLock 公平锁与非公平锁的源码实现分析

一、ReentrantLock 的锁类型
ReentrantLock 内部通过 Sync 类(继承自 AbstractQueuedSynchronizer)实现锁机制,其子类 FairSync(公平锁)和 NonfairSync(非公平锁)分别对应两种模式:

// ReentrantLock 构造函数(默认非公平锁)
public ReentrantLock() {
    sync = new NonfairSync();
}

// 指定公平性的构造函数
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

二、非公平锁(NonfairSync)的实现

  1. lock() 方法
    非公平锁在尝试获取锁时,直接通过 CAS 抢占资源,不检查等待队列:

    final void lock() {
     if (compareAndSetState(0, 1))  // 直接尝试 CAS 抢锁
         setExclusiveOwnerThread(Thread.currentThread());
     else
         acquire(1);  // 进入 AQS 队列等待
    }
    
  2. tryAcquire() 方法
    在 nonfairTryAcquire 中,直接尝试修改 state,无视队列中的等待线程:
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)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

关键点:

抢占式获取:直接 CAS 尝试获取锁,不检查队列是否有等待线程。

高吞吐量:减少线程切换,但可能导致饥饿。

三、公平锁(FairSync)的实现

  1. lock() 方法
    公平锁直接调用 acquire(),进入队列等待:

    final void lock() {
     acquire(1);  // 直接进入 AQS 队列排队
    }
  2. tryAcquire() 方法
    在尝试获取锁前,先检查队列中是否有等待线程(hasQueuedPredecessors()):

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

关键点:

队列检查:hasQueuedPredecessors() 确保只有队列为空或当前线程是队列头节点时,才能获取锁。

公平性保证:严格遵循 FIFO 顺序,避免饥饿。

四、核心方法:hasQueuedPredecessors()
检查队列中是否有优先级更高的等待线程:

public final boolean hasQueuedPredecessors() {
    Node h, s;
    if ((h = head) != null) {
        if ((s = h.next) == null || s.waitStatus > 0) {
            s = null; // 遍历队列找到第一个有效节点
            for (Node p = tail; p != h && p != null; p = p.prev) {
                if (p.waitStatus <= 0)
                    s = p;
            }
        }
        if (s != null && s.thread != Thread.currentThread())
            return true;  // 存在其他等待线程
    }
    return false;
}

五、对比总结
特性 非公平锁(NonfairSync) 公平锁(FairSync)
锁获取策略 直接 CAS 抢占,无视队列 先检查队列,仅当队列无等待时获取
吞吐量 高(减少线程切换) 较低(频繁上下文切换)
饥饿问题 可能发生饥饿 避免饥饿
适用场景 高并发且线程持有锁时间短的场景 要求公平性的场景(如任务调度)

六、源码设计思想
模板方法模式:AQS 提供骨架方法(如 acquire()),子类重写 tryAcquire() 实现具体逻辑。

状态管理:通过 state 变量实现锁的重入计数和许可证管理。

队列管理:CLH 队列变种高效处理线程排队与唤醒。

七、实战建议
默认使用非公平锁:在大多数场景下,非公平锁的性能更优。

谨慎使用公平锁:仅在严格要求顺序性的场景(如避免饥饿)时使用。

你可能感兴趣的:(java)