- 原文地址:Semaphores
- 作者: Jakob Jenkov
信号量作为一种线程同步机制,可以用于解决线程间信号丢失问题,也可以当作Lock来保护临界区使用。 Java从5开始在
java.util.concurrent
包中引入信号量实现。 虽然,不用再自己实现一个信号量,但是还是那句话:只有知道背后的机制才能更好的使用。
Java 5 提供了一个
java.util.concurrent.Semaphore
实现类,更多信息,可以参见Jakob Jenkov的博客文章。
简单的信号量
先来看看一个简单的信号量实现:
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()
。 如果take()
先于release()
被调用,而由于信号是存储在内部signal
变量上的,所以release()
就会知道前面已经调用过take()
了。 这样就避免了直接使用wait()
和notify()
时,存在的信号丢失问题。
不过用
take()
和release()
来表述信号量发送,似乎不怎么贴切。 从这个命名上来看,其实更贴近锁的概念,稍后会介绍将信号量当作锁来使用。
使用信号量来发送信号
再来看看在两个线程之间通过信号量发送信号的例子:
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()
方法发送的信号进行计数。 下面再来看看计数信号量的实现:
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--;
}
}
有界信号量
上面的
CoutingSemaphore
并没有对信号量数量进行限定。 我们可以在信号量数量上进行一个限定:
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()
才能够继续投递信号。
将信号量当作锁来使用
可以使用一个上限为1的信号量当作锁来使用。 这样就可以用
take()
和release()
来保护临界区。
BoundedSemaphore semaphore = new BoundedSemaphore(1);
...
semaphore.take();
try{
//critical section
} finally {
semaphore.release();
}
由于只能有一个线程能够获得信号,其他线程都只能阻塞等待。
也可以使用信号量的数量来允许多个线程同时进入临界区。 如果在上述例子中设置信号量限制数为5,那就会允许5个线程同时进入临界区。 如果这样,就需要确保5个线程不会互相冲突 。
最后,需要注意几个地方:
- 要确保
release()
在finally块中调用- 需要注意,将信号量当作锁使用时,相当于不可重入锁