信号量Semaphore深入解读
1 简介
Semaphore
可翻译为信号量,它维护一组许可证, 每次尝试获取许可证时都将阻塞等待直到可获取,它才能获取到并解除阻塞状态。 Semaphore
可以控制一些物理或逻辑资源的访问或使用,它常常用于限制线程数目。在实际开发中,可用作流量控制,特别对于一些公共资源有限的应用场景,如数据库连接,或是一些其他限流的缓存池。(基于JDK1.8)
2 示例
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并发编程的艺术》方腾飞