# 前言

Semaphore 是一种计数信号量,限定只能获取锁 n 次。也是基于 AQS 实现的同步锁,支持公平和非公平锁。

# 使用示例

Java 线程同步器

# 源码分析

和 CountDownLatch 一样,用 Sync 继承 AQS 抽象类,依赖共享锁。支持 NonfairSync 和 FairSync 模式。

public Semaphore(int permits) {
    // 默认非公平模式,指定资源最大持有线程数
    sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
    // 指定为公平模式
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

Semaphore 提供的主要方法有:

  • acquire()
  • acquireUninterruptibly()
  • tryAcquire()
  • tryAcquire(long timeout, TimeUnit unit)
  • release()
  • acquire(int permits)
  • acquireUninterruptibly(int permits)
  • tryAcquire(int permits)
  • tryAcquire(int permits, long timeout, TimeUnit unit)
  • release(int permits)

# Sync

它是一个同步器的基础抽象类,实现了非公平的尝试获取锁,和释放锁的操作。初始化时指定许可数量,设置 state 为对应值,每次获取锁时,对 state 进行减操作,释放锁时,对 state 进行加操作。

Sync(int permits) {
    setState(permits);
}
final int nonfairTryAcquireShared(int acquires) {
    // 这里在 try 的时候就开始自旋了,当还有剩余资源时,其他线程来抢占锁导致 CAS 更新 state 失败之后,这时是不会进入等待队列的,会再次尝试获取锁。只有当没有剩余资源的时候,才会进入等待队列。
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}
protected final 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;
    }
}

# NonfairSync

非公平模式比较简单,父类 Sync 已经帮忙实现了,简单地重写下 tryAcquireShared 就行了。

protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}

# FairSync

公平模式下,需要看下等待队列是否已经有线程在排队,如果有线程在排队,不能插队。

protected int tryAcquireShared(int acquires) {
    for (;;) {
        // 这里不能插队
        if (hasQueuedPredecessors())
            return -1;
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

# 总结

基于 AQS 实现的同步锁,有 ReentrantLock、CountDownLatch 和 Semaphore 等。ReentrantLock 和 Semaphore 都支持了公平与非公平模式。

ReentrantLock 是一种简单的同步锁,支持重入,用 state 记录获取锁的次数。非公平模式下,如果 state 为 0 或持有锁的线程是当前线程就 CAS 尝试获取锁,否则就尝试获取锁失败,交给 AQS 进行排队。公平模式与非公平模式不同的是,state 为 0 时需要先判断下等待队列中有没有别的线程在。两种模式下的释放锁都是一样的,对 state 进行减操作,前提是拿锁的是当前线程。

Semaphore 规定了最多只能获取锁 n 次,同样支持公平和非公平模式。初始 state 值为最大资源获取次数,每当有线程获取 m 次,就会减 m,剩余资源不足 m 时,就会进入等待队列进行等待。与 ReentrantLock 不同的是,释放锁时没有限制必须是获取锁的线程,因为它是一种线程间的信号量,也可以先释放锁,再获取锁,两者没有强依赖关系。

CountDownLatch 也是一种线程间进行通信的方式,m 个线程需要等另外 n 个线程完成某个动作,才能继续。初始化 state 为 n,依赖 AQS 的共享锁,默认都持有锁,当 n 个线程都释放锁时,将 state 置为 0,会唤醒等待的 m 个线程。m 个线程调用获取锁的方法进行等待,也只有 state 为 0 时才能获取到锁。

由上可以看出,线程间如何通信,完全依赖于如果定义 state,如果定义尝试获取锁和释放锁的逻辑。