1.什么是信号量
学计算机的小伙伴应该都记得以前在操作系统课程中被信号量的P,V操作折磨的日子,当时只是抽象的说这里需要P,那里需要V的,很难结合实际的应用去使用信号量。而在Java中就实现了一种特别好用的信号量Semaphore。Java中的Semaphore大多数情况下可以作为为计数信号量,用来控制同时访问某个特定资源的操作数量,或者是执行某个特定操作的数量。计数信号量还可以用来实现某种资源池,或者是对容器添加边界。
Semaphore中管理着一组虚拟的许可,许可的初始数量可以通过构造函数来指定。在执行操作时可以首先获得许可,在使用过后释放许可,如果没有许可,那么aquire将阻塞直到有许可(或者直到被中断或者操作超时)。release方法将返回一个许可给信号量。
Semaphore也可以作为计算信号量,计算信号量的一种简化形式就是二值信号量,即初始值为1的Semaphore,二值信号量可以用作互斥体(mutex)并具备不可重入的加锁语义,谁拥有这个唯一许可,就拥有了互斥锁
2.信号量的应用场景
Semaphore可以用于实现资源池,例如数据库连接池,我们可以构造一个固定长度的资源池,当池为空时,请求资源将会失败,但是我们真正希望看到的行为时阻塞而不是失败,并且当池非空的时候解除阻塞,如果将Semaphore的计数值初始化为池的大小,并在池中获取一个资源之前首先调用acquire方法获取一个许可,将资源返回给资源池之后调用release释放许可。
3.示例解析
我们可以使用Semaphore将任何一种容器变成有界的阻塞容器。例如,我们可以利用Semaphore将一个Set容器改造为一个阻塞容器,代码如下:
public class BoundedHashSet<T> {
private final Set<T> set;
private final Semaphore sem;
public BoundedHashSet(int bounded) {
this.set = Collections.synchronizedSet(new HashSet<T>());
sem = new Semaphore(bounded);
}
public boolean add(T e) throws InterruptedException {
sem.acquire();
boolean wasAdded = false;
try {
wasAdded = set.add(e);
return wasAdded;
} finally {
if (!wasAdded) {
sem.release();
}
}
}
public boolean remove(T e) {
boolean wasRemoved = set.remove(e);
if (wasRemoved) {
sem.release();
}
return wasRemoved;
}
public static void main(String[] args) throws InterruptedException {
BoundedHashSet<Integer> boundedHashSet = new
BoundedHashSet<>(3);
boundedHashSet.add(1);
boundedHashSet.add(2);
boundedHashSet.add(3);
boundedHashSet.add(4);
boundedHashSet.remove(3);
}
}
如上面代码所示,信号量的计数值会初始化为容器的的最大值,add操作在向底层容器中添加一个元素之前,首先要获取一个许可。如果add操作没有添加任何元素没那么会释放许可。同样,remove操作会释放一个许可,使更多的元素能够添加到容器中,底层的Set实现并不知道关于边界的任何信息,这些都是由BoundedHashSet来处理。