从AQS看ReentrantLock、Semaphore与CountDownLatch的源码级差异

133 阅读3分钟

一、AQS的两种模式:独占与共享

AQS通过tryAcquire/tryReleasetryAcquireShared/tryReleaseShared两套方法,支持两种资源访问模式:

  1. 独占模式(Exclusive):资源仅允许一个线程持有(如ReentrantLock)。
  2. 共享模式(Shared):资源允许多个线程共同访问(如Semaphore、CountDownLatch)。

二、ReentrantLock:独占锁的实现

1. 核心代码解析
  • state含义:表示锁的重入次数(0=未锁定,≥1=被持有)。
  • Sync实现
    // ReentrantLock.Sync
    protected final boolean tryAcquire(int acquires) {
        // 实现重入逻辑(见前文)
    }
    
    protected final boolean tryRelease(int releases) {
        // 减少state,归零时释放锁
    }
    
2. 特点
  • 同一时刻只有一个线程能修改state
  • AQS队列中所有节点均为Node.EXCLUSIVE(独占模式)。

三、Semaphore:共享锁的流量控制

1. 核心代码解析
  • state含义:表示可用许可证数量。
  • Sync实现
    // Semaphore.NonfairSync
    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
    
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 || 
                compareAndSetState(available, remaining)) {
                return remaining;  // 正数:获取成功;负数:需排队
            }
        }
    }
    
    protected boolean tryReleaseShared(int releases) {
        for (;;) {
            int current = getState();
            int next = current + releases;
            if (next < current) throw new Error("Maximum permit count exceeded");
            if (compareAndSetState(current, next)) 
                return true;
        }
    }
    
2. 特点
  • 多个线程可同时获取许可证(state减少),直到归零。
  • AQS队列节点为Node.SHARED,唤醒时会传播(连续唤醒后续共享节点)。

四、CountDownLatch:一次性栅栏

1. 核心代码解析
  • state含义:初始值为计数阈值,递减至0时触发唤醒。
  • Sync实现
    // CountDownLatch.Sync
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;  // 1=成功,-1=需阻塞
    }
    
    protected boolean tryReleaseShared(int releases) {
        for (;;) {
            int c = getState();
            if (c == 0) return false;
            int nextc = c - 1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;  // 只有当state归零时返回true,触发唤醒
        }
    }
    
2. 工作流程
  1. 初始化state = N(N为计数阈值)。
  2. countDown():调用releaseShared(1),递减state,归零时唤醒所有等待线程。
  3. await():调用acquireSharedInterruptibly(1),若state ≠ 0,线程进入队列阻塞。

五、三者的关键差异对比

维度ReentrantLockSemaphoreCountDownLatch
同步模式独占共享共享
state语义重入次数剩余许可证数量剩余计数
资源释放持有线程主动释放获取许可证的线程均可释放外部线程调用countDown()
重用性可重入,反复加锁/解锁许可证可动态增减一次性(归零后不可重置)
典型场景临界区互斥访问限流(如数据库连接池)多线程等待事件触发

六、AQS的通用逻辑与定制扩展

1. 共性逻辑
  • 队列管理:所有同步器共用同一套入队/出队、阻塞/唤醒逻辑。
  • 中断与超时:由AQS统一处理,子类无需关心。
2. 定制扩展

通过重写以下方法实现不同同步语义:

  • 独占模式
    protected boolean tryAcquire(int arg)
    protected boolean tryRelease(int arg)
    
  • 共享模式
    protected int tryAcquireShared(int arg)
    protected boolean tryReleaseShared(int arg)
    

七、总结:AQS的抽象力量

  • ReentrantLock:通过state和独占模式实现互斥性与重入性。
  • Semaphore:利用共享模式和state的原子操作,实现灵活的许可证管理。
  • CountDownLatch:通过state的递减和共享唤醒机制,构建一次性协作屏障。

源码启示:AQS通过抽象state的含义和操作,将复杂的线程调度封装为可复用的框架,这正是Java并发包高效且灵活的核心原因。