1 简介
Semaphore
可翻译为信号量,它维护一组许可证, 每次尝试获取许可证时都将阻塞等待直到可获取,它才能获取到并解除阻塞状态。 Semaphore
可以控制一些物理或逻辑资源的访问或使用,它常常用于限制线程数目。在实际开发中,可用作流量控制,特别对于一些公共资源有限的应用场景,如数据库连接,或是一些其他限流的缓存池。(基于JDK1.8)
2 示例
这是一个使用信号量控制对缓存池中items访问的示例。
public class SemaphoreDemo {
static class Pool {
private static final int MAX_AVAILABLE = 6;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
}
public void putItem(Object x) {
if (markAsUnused(x))
available.release();
}
protected Object[] items;
protected boolean[] used = new boolean[MAX_AVAILABLE];
Pool() {
items = new Object[15];
for (int i = 0; i < items.length; i++) {
items[i] = "item" + i;
}
}
protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null; // not reached
}
protected synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else
return false;
}
}
return false;
}
}
public static void main(String[] args) {
final int THREAD_COUNT = 10;
ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
Pool pool = new Pool();
for (int i = 0; i < THREAD_COUNT; i++) {
int tmpI = i;
threadPool.submit(() -> {
try {
Object item = pool.getItem();
System.out.printf("当前线程:%s,获取到缓存池中的资源:%s\n", Thread.currentThread().getName(), item);
Thread.sleep(7);
pool.putItem(item);
System.out.printf("当前线程:%s,已将缓存池中的资源%s放回池中\n", Thread.currentThread().getName(), item);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
threadPool.shutdown();
}
}
假如有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发地读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个,这时我们必须控制只有10个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接.
class SemaphoreTest {
private static final int THREAD_COUNT = 30;
private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
private static Semaphore s = new Semaphore(10);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
s.acquire();
System.out.println("save data");
s.release();
} catch (InterruptedException e) {
}
}
});
}
threadPool.shutdown();
}
}
3 实现原理
Semaphore
内部主要有一个Sync
类型成员变量sync
, Sync
是继承抽象类AbstractQueuedSynchronizer的静态抽象内部类。
Semaphore
利用父类AQS
实现了一个共享锁,Sync有两个子类NonfairSync
和FairSync
,分另代表非公平锁、公平锁。共享锁的关键在于实现重写tryAcquireShared
和 tryReleaseShared
方法,这两个方法分别会被父类的模板方法acquireShared
、releaseShared
所调用,具体细节请看AbstractQueuedSynchronizer实现原理分析。
Semaphore的默认构造方法使用非公平锁,Semaphore的构造方法有一个布尔型可选参数fair,此参数指定锁的公平锁。
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
(1) 静态内部类Sync
构造方法Sync(int)
将父类AbstractQueuedSynchronizer
的实例变量state
设置为指定的许可证数permits
。
Sync(int permits) {
setState(permits);//
}
getPermits()`返回的许可证数即是父类AQS的state值.
final int getPermits() {
return getState();
}
nonfairTryAcquireShared
是非公平锁尝试释获取锁的方法,每成功获取一次锁,就从池中拿走一个许可证,而剩余的许可证就少1个。
其主要逻辑是: CAS自旋直到成功将state减1,并返回新的state,或当前已获取到的许可证数超出了设定的许可证总数,方法返回。
此方法返回负数时,线程将被阻塞。
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;//acquires一般是1
if (remaining < 0 ||//为负数,表示超出了设定的许可证总数,直接返回。不能再获取许可证
compareAndSetState(available, remaining))
return remaining;
}
}
公平锁与非公平锁释放锁状态的逻辑是一样的,都会执行tryReleaseShared
方法。每释放一把锁,就将一个许可证放回池中,可用的许可证就多一个。
其主要逻辑是: CAS自旋直到成功将state加1,最终返回true.
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;//acquires一般是1
if (next < current) // overflow next成为负数,next超出int类型的最大可表示范围
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
reducePermits减少许可证数,逻辑与nonfairTryAcquireShared
类似,其逻辑是:CAS自旋尝试将state
减少指定数目reductions
。
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
drainPermits将所有许可证拿走,其逻辑是:CAS自旋尝试将state
设为0,并返回当前所有可用的许可证。
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
非公平的Sync:NonfairSync
NonfairSync
代表一个公平锁的实现,它并没有自己的逻辑,其tryAcquireShared方法也是直接调用父类的nonfairTryAcquireShared
方法。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
公平的Sync: FairSync
FairSync
代表一个公平锁的实现,它的tryAcquireShared方法有自己的逻辑,与兄弟类NonfairSync的不同之处在于多了等待队列上是否存在其前驱节点的判断。
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
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;
}
}
}
(2) 获取许可证
acquireUninterruptibly
获取一个许可证不响应中断,若获取失败,将阻塞等待直到可获取为止
acquire
获取一个许可证会响应中断,若获取失败,将阻塞等待直到可获取为止
public void acquireUninterruptibly() {
sync.acquireShared(1);
}
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
acquire(int)
一性次获取多个许可证,响应中断。若获取失败,将阻塞等待直到可获取为止
acquireUninterruptibly(int)
一性次获取多个许可证,不响应中断。,若获取失败,将阻塞等待直到可获取为止
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
}
tryAcquire()
尝试一个获取许可证,获取失败直接返回false,不会阻塞等待。
tryAcquire(long,TimeUnit)
尝试超时获取一个许可证,在限定时间内获取许可证失败返回false,不会一直阻塞等待。
public boolean tryAcquire() {
return sync.nonfairTryAcquireShared(1) >= 0;
}
public boolean tryAcquire(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
tryAcquire(int)
尝试多个获取许可证,获取失败直接返回false,不会阻塞等待。
tryAcquire(int,long,TimeUnit)
尝试超时获取多个许可证,在限定时间内获取许可证失败返回false,不会一直阻塞等待。
public boolean tryAcquire(int permits) {
if (permits < 0) throw new IllegalArgumentException();
return sync.nonfairTryAcquireShared(permits) >= 0;
}
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}
drainPermits
获取并返回当前所有可用的许可证
public int drainPermits() {
return sync.drainPermits();
}
(3) 释放许可证
release()
释放一个许可证
release(int)
释放多个许可证
public void release() {
sync.releaseShared(1);
}
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
(4) 减少许可证
reducePermits
是protected级别方法,外部不可见,主要提供给子类在定制化某些功能时调用。
reducePermits
与获取许可证方法acquireXX
不同,sync.reducePermits
不是锁的相关方法,不被父类AQS的模板方法调用。
reducePermits
只是单单减少许可证数,不会阻塞线程。
protected void reducePermits(int reduction) {
if (reduction < 0) throw new IllegalArgumentException();
sync.reducePermits(reduction);
}
(5) 状态查询
availablePermits
返回当前可获取的许可证数。
isFair
返回当前锁的公平性。
hasQueuedThreads
返回当前是否有线程因获取许可证而阻塞等待。
getQueueLength
返回当前因获取许可证而阻塞等待的线程数。
getQueuedThreads
返回当前因获取许可证而阻塞等待的线程集合;此方法为protected级别,外界不可见,主要方便子类监控相关指标。
public int availablePermits() {
return sync.getPermits();
}
public boolean isFair() {
return sync instanceof FairSync;
}
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
public final int getQueueLength() {
return sync.getQueueLength();
}
protected Collection<Thread> getQueuedThreads() {
return sync.getQueuedThreads();
}
参考: 《 Java并发编程的艺术》方腾飞