# 前言
Semaphore 是一种计数信号量,限定只能获取锁 n 次。也是基于 AQS 实现的同步锁,支持公平和非公平锁。
# 使用示例
# 源码分析
和 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,如果定义尝试获取锁和释放锁的逻辑。