一个信号量是线程同步的概念,它可以用来在线程之间发送信号去为了避免丢失标志,或者去监控像你使用锁的一个临界区域。Java 5在java.util.concurrent包中带来了信号量的实现,以至于你不需要实现你自己的。当然,知道它背后的原理并且去使用它是有用的。
Java 5带来了内建的Semaphore,以至于你不需要去实现它。你可以在java.util.concurrent.Semaphore类中读到更多关于它的内容。
这里有一个简单的Semaphore的实现:
public class Semaphore {
private boolean signal = false;
public synchronized void take() {
this.signal = true;
this.notify();
}
public synchronized void release() throws InterruptedException{
while(!this.signal) wait();
this.signal = false;
}
}
这个take方法发送了信号,它存储在Semaphore的内部。这个release方法等待一个信号。当接收到这个信号标识再次被清除之后,这个release方法就会退出了。
像这样使用一个信号,你可以避免丢失信号。你将会通过调用take方法代替notify方法,并且release方法代替wait方法。如果在调用release方法之前调用take方法,那调用release方法的这个线程将会知道这个take方法被调用了,因为这个signal变量存储在内部。这个跟wait方法和notify方法就不一样了。
当为了发信号使用信号量的时候,这个take方法和release方法的名字可能看起来有点怪。这个名字最早来自于信号量作为锁的使用,这个将会在后面的内容中做解释。既然那样,这个名字就更有意义了。
这里有一个简单的例子,两个线程彼此互相使用一个Semaphore发送信号:
Semaphore semaphore = new Semaphore();
SendingThread sender = new SendingThread(semaphore);
ReceivingThread receiver = new ReceivingThread(semaphore);
receiver.start();
sender.start();
public class SendingThread {
Semaphore semaphore = null;
public SendingThread(Semaphore semaphore){
this.semaphore = semaphore;
}
public void run(){
while(true){
//do something, then signal
this.semaphore.take();
}
}
}
public class RecevingThread {
Semaphore semaphore = null;
public ReceivingThread(Semaphore semaphore){
this.semaphore = semaphore;
}
public void run(){
while(true){
this.semaphore.release();
//receive signal, then do something...
}
}
}
在前面实现的那个Semaphore不能记录被take方法调用的发送出去的信号数量。我们可以修改Semaphore去做到这样。这个就是调用一个计数的信号量。这里有一个简单的计数信号量的实现:
public class CountingSemaphore {
private int signals = 0;
public synchronized void take() {
this.signals++;
this.notify();
}
public synchronized void release() throws InterruptedException{
while(this.signals == 0) wait();
this.signals--;
}
}
这个CountingSemaphore对于它可以存储多少个信号是没有上限的。我们可以去修改它,让它有一个上限,像这样:
public class BoundedSemaphore {
private int signals = 0;
private int bound = 0;
public BoundedSemaphore(int upperBound){
this.bound = upperBound;
}
public synchronized void take() throws InterruptedException{
while(this.signals == bound) wait();
this.signals++;
this.notify();
}
public synchronized void release() throws InterruptedException{
while(this.signals == 0) wait();
this.signals--;
this.notify();
}
}
注意这个take方法现在如果信号的数量达到上限之后将会锁住。直到一个线程调用release方法之后调用take方法的这个线程才允许释放它的的信号,如果这个BoundedSemaphore已经达到了它的上限。
使用一个有界限的信号量作为锁是可能的。为了这样,设置这个上限为1,对于take方法和release方法的调用监视着这个临界区域。这里有一个例子:
BoundedSemaphore semaphore = new BoundedSemaphore(1);
...
semaphore.take();
try{
//critical section
} finally {
semaphore.release();
}
与发送信号的例子形成对比,现在这个take方法和release方法被相同的线程调用。因为只有一个线程可以获取到这个信号,所有其他的调用take方法的线程将会锁住知道release方法被调用。对于release方法的调用将不会锁住,因为这里将会总是先有一个take方法的调用。
你也可以通过使用有限信号量去限制允许进入这个代码区域的线程的数量。例如,在上面的例子中,如果你设置了这个BoundedSemaphore为5,将会发生什么呢?一次将会只允许5个线程进入到这个临界区域,然后你需要确认,这5个线程的操作不会有什么冲突,否则你的应用将会失败。
这个release方法放在finally代码块中被执行,是为了确保代码抛出异常的时候也会被调用到。
翻译地址:http://tutorials.jenkov.com/java-concurrency/semaphores.html