下面的代码是我从Semaphore上的oracle文档中获取的。
我的问题是,如果我无论如何都在同步getNextAvailableItem()和markAsUnused方法,这将阻止其他99个线程进入主函数,该函数要么给我共享资源,要么接受它。那么信号量的用途是什么,因为不管是99还是1000个线程在等待锁定。
class Pool {
private static final int MAX_AVAILABLE = 100;
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();
}
// Not a particularly efficient data structure; just for demo
protected Object[] items = ... whatever kinds of items being managed
protected boolean[] used = new boolean[MAX_AVAILABLE];
protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null;
}
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;
}
}
问题是:是否希望线程阻塞,直到有可用项?
如果是的话,那么信号量是必要的,因为它基本上是一个计数器,当它已经为零时就会阻塞,并且线程正在试图获取它
很明显,您可以尝试在没有信号量的同步方法中自己实现这一点(即使用一些低级别的构造,如wait
/notify
),但这只是对难以发现的错误的邀请。
如果不需要阻塞,并且您对方法向调用方返回null很满意,那么您可以跳过信号量。
要问的问题是,如果调用getItem
时项不可用,该怎么办。很明显,这里的要求是等待一个可用的。仅使用synchronized
是行不通的,因为它不允许在解锁状态下等待(在锁定状态下等待会阻止任何人释放任何东西)。
计数信号量是实现这一点的最有效方法。当您的available.acquire();
成功返回(即不抛出InterruptedException
)时,已知至少有一个项目可用(因为项目计数与信号量的初始计数匹配,并且可用项目的计数与信号值计数器保持同步)。
有其他选择,但没有那么好。
您可以使用互斥和条件变量,在Java中称为Lock
和Condition
(或者您也可以使用Java的wait
和notify
以及synchronized
)。getItem
将有一个循环:锁定互斥锁,查看项是否可用:如果可用,则获取它并返回,否则等待条件变量并重试。然后putItem
会在发布项目后发出条件变量的信号,这会唤醒任何服务员,这样他们就知道要检查是否有空。但这是一个更多的代码,可能效率更低(尤其是如果所有服务员都被唤醒,而不是只有一个),而且Java Condition
没有提供公平排队的方法,而Semaphore
具有公平布尔属性,所以服务员是按顺序服务的。
然后,一个更糟糕的选择是只有一个Lock
或synchronized
块,然后在循环中检查项目是否可用,并在可能的情况下接受它,否则(可选)睡眠一段时间,然后重试。但这是低效的,丑陋的,不能扩展到许多线程,并且会让任何有经验的开发人员感到尴尬。