一、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 尝试获取锁,不检查队列是否有等待线程。

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

3. tryAcquire() 方法和 lock() 方法的主要区别如下

功能与行为:

lock() 方法是一个阻塞式的加锁方法。
如果锁已经被其他线程持有,调用 lock() 的线程会一直等待,直到获取到锁为止。
tryAcquire() 方法是一个非阻塞式的尝试加锁方法。
如果锁未被持有,它会尝试获取锁并返回 true;如果锁已被持有,它会立即返回 false,而不会阻塞线程。

实现机制:

lock() 方法内部会调用 acquire() 方法,而 acquire() 方法会进一步调用 tryAcquire() 来尝试获取锁。
如果 tryAcquire() 失败,线程会被加入到等待队列中,进入阻塞状态。
tryAcquire() 是 ReentrantLock 内部实现的核心方法,具体逻辑由公平锁(FairSync)
和非公平锁(NonfairSync)分别实现。公平锁会严格按照等待队列的顺序获取锁,而非公平锁则允许插队。

使用场景:

lock() 方法适用于需要确保线程一定能够获取到锁的场景,即使需要等待。
tryAcquire() 方法适用于需要尝试获取锁但不希望线程阻塞的场景,例如在尝试获取锁失败时执行其他逻辑。

超时机制:

lock() 方法没有超时机制,线程会一直等待直到获取锁。
tryAcquire() 可以通过 tryAcquireNanos() 方法实现带超时的尝试获取锁,允许线程在指定时间内等待锁。

总结来说,lock() 是阻塞式的加锁方法,适合必须获取锁的场景;而 tryAcquire() 是非阻塞式的尝试加锁方法,适合需要灵活处理锁获取失败的场景。

三、公平锁(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 队列变种高效处理线程排队与唤醒。

七、ReentrantLock 与 ReentrantReadWriteLock 的区别

锁的类型:

ReentrantLock 是一种互斥锁,无论是读操作还是写操作,都需要独占锁资源。
ReentrantReadWriteLock 是一种读写锁,将锁分为读锁和写锁,读锁可以共享,写锁是独占的。

锁的粒度:

ReentrantLock 的锁粒度较粗,读读、读写、写写操作都会互斥。
ReentrantReadWriteLock 的锁粒度更细,支持读读共享,读写和写写互斥,适合读多写少的场景。

性能:

ReentrantLock 在高并发读场景下性能较低,因为所有操作都需要互斥。
ReentrantReadWriteLock 在读多写少的场景下性能更高,因为读操作可以并发执行。

锁降级:

ReentrantLock 不支持锁降级。
ReentrantReadWriteLock 支持锁降级,即写锁可以降级为读锁,保证数据可见性。

使用场景:

ReentrantLock 适合需要独占锁的场景,如对共享资源的写操作。
ReentrantReadWriteLock 适合读多写少的场景,如缓存系统。

总结

ReentrantLock 是一种通用的互斥锁,适合需要独占锁的场景。
ReentrantReadWriteLock 通过分离读锁和写锁,提高了读操作的并发性能,适合读多写少的场景

高旭
40 声望3 粉丝