分享

java并发(十八)信号量

 roydocs 2015-04-15
Semaphore(信号量) 是一个线程同步结构,用于在线程间传递信号,以避免出现信号丢失(译者注:下文会具体介绍),或者像锁一样用于保护一个关键区域。自从5.0开始,jdk在java.util.concurrent包里提供了Semaphore 的官方实现,因此大家不需要自己去实现Semaphore。但是还是很有必要去熟悉如何使用Semaphore及其背后的原理。



本文的涉及的主题如下:



简单的Semaphore实现

使用Semaphore来发出信号

可计数的Semaphore

有上限的Semaphore

把Semaphore当锁来使用

JAVA的信号量接口实现

一、简单的Semaphore实现

下面是一个信号量的简单实现:

Java代码 复制代码 收藏代码
  1. public class Semaphore {  
  2.     private boolean signal = false;  
  3.   
  4.     public synchronized void take() {  
  5.         this.signal = true;  
  6.         this.notify();  
  7.     }  
  8.   
  9.     public synchronized void release() throws InterruptedException {  
  10.         while (!this.signal)  
  11.             wait();  
  12.         this.signal = false;  
  13.     }  
  14. }  


Take方法发出一个被存放在Semaphore内部的信号,而Release方法则等待一个信号,当其接收到信号后,标记位signal被清空,然后该方法终止。



使用这个semaphore可以避免错失某些信号通知。用take方法来代替notify,release方法来代替wait。如果某线程在调用release等待之前调用take方法,那么调用release方法的线程仍然知道take方法已经被某个线程调用过了,因为该Semaphore内部保存了take方法发出的信号。而wait和notify方法就没有这样的功能。



当用semaphore来产生信号时,take和release这两个方法名看起来有点奇怪。这两个名字来源于后面把semaphore当做锁的例子,后面会详细介绍这个例子,在该例子中,take和release这两个名字会变得很合理。



二、使用Semaphore来产生信号

下面的例子中,两个线程通过Semaphore发出的信号来通知对方

Java代码 复制代码 收藏代码
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.         Semaphore semaphore = new Semaphore();  
  5.         SendingThread sender = new SendingThread(semaphore);  
  6.         ReceivingThread receiver = new ReceivingThread(semaphore);  
  7.         receiver.start();  
  8.         sender.start();  
  9.   
  10.     }  
  11. }  
  12. public class ReceivingThread extends Thread {  
  13.     Semaphore semaphore = null;  
  14.   
  15.     public ReceivingThread(Semaphore semaphore) {  
  16.         this.semaphore = semaphore;  
  17.     }  
  18.   
  19.     public void run()  {  
  20.         while (true) {  
  21.             try {  
  22.                 this.semaphore.release();  
  23.             } catch (Exception e) {  
  24.             }  
  25.             // receive signal, then do something...  
  26.         }  
  27.     }  
  28. }  
  29. public class SendingThread extends Thread {  
  30.     Semaphore semaphore = null;  
  31.   
  32.     public SendingThread(Semaphore semaphore) {  
  33.         this.semaphore = semaphore;  
  34.     }  
  35.   
  36.     public void run() {  
  37.         while (true) {  
  38.             // do something, then signal  
  39.             this.semaphore.take();  
  40.         }  
  41.     }  
  42. }  


三、可计数的Semaphore

上面提到的Semaphore的简单实现并没有计算通过调用take方法所产生信号的数量。可以把它改造成具有计数功能的Semaphore。下面是一个可计数的Semaphore的简单实现。

Java代码 复制代码 收藏代码
  1. public class CountingSemaphore {  
  2.     private int signals = 0;  
  3.   
  4.     public synchronized void take() {  
  5.         this.signals++;  
  6.         this.notify();  
  7.     }  
  8.   
  9.     public synchronized void release() throws InterruptedException {  
  10.         while (this.signals == 0)  
  11.             wait();  
  12.         this.signals--;  
  13.     }  
  14. }  


四、有上限的Semaphore

上面的CountingSemaphore并没有限制信号的数量。下面的代码将CountingSemaphore改造成一个信号数量有上限的BoundedSemaphore。

Java代码 复制代码 收藏代码
  1. public class BoundedSemaphore {  
  2.     private int signals = 0;  
  3.     private int bound = 0;  
  4.   
  5.     public BoundedSemaphore(int upperBound) {  
  6.         this.bound = upperBound;  
  7.     }  
  8.   
  9.     public synchronized void take() throws InterruptedException {  
  10.         while (this.signals == bound)  
  11.             wait();  
  12.         this.signals++;  
  13.         this.notify();  
  14.     }  
  15.   
  16.     public synchronized void release() throws InterruptedException {  
  17.         while (this.signals == 0)  
  18.             wait();  
  19.         this.signals--;  
  20.         this.notify();  
  21.     }  
  22. }  


在BoundedSemaphore中,当已经产生的信号数量达到了上限,take方法将阻塞新的信号产生请求,直到某个线程调用release方法后,被阻塞于take方法的线程才能传递自己的信号。



五、把Semaphore当锁来使用

当信号量的数量上限是1时,Semaphore可以被当做锁来使用。通过take和release方法来保护关键区域。请看下面的例子:

Java代码 复制代码 收藏代码
  1. BoundedSemaphore semaphore = new BoundedSemaphore(1);  
  2. ...  
  3. semaphore.take();  
  4. try{  
  5. //critical section  
  6. finally {  
  7. semaphore.release();  
  8. }  


在前面的例子中,Semaphore被用来在多个线程之间传递信号,这种情况下,take和release分别被不同的线程调用。但是在锁这个例子中,take和release方法将被同一线程调用,因为只允许一个线程来获取信号(允许进入关键区域的信号),其它调用take方法获取信号的线程将被阻塞,知道第一个调用take方法的线程调用release方法来释放信号。对release方法的调用永远不会被阻塞,这是因为任何一个线程都是先调用take方法,然后再调用release。



通过有上限的Semaphore可以限制进入某代码块的线程数量。设想一下,在上面的例子中,如果BoundedSemaphore 上限设为5将会发生什么?意味着允许5个线程同时访问关键区域,但是你必须保证,这个5个线程不会互相冲突。否则你的应用程序将不能正常运行。



必须注意,release方法应当在finally块中被执行。这样可以保在关键区域的代码抛出异常的情况下,信号也一定会被释放。



六、JAVA的信号量接口实现

Java代码 复制代码 收藏代码
  1. Semaphore semaphore = new Semaphore(0);  
  2. semaphore.acquire(2); // 获取许可  
  3. semaphore.release();//授权  
  4. semaphore.release(2);//授权  


new Semaphore(0)表示初始状态,semaphore.acquire全部都阻塞,等待授权发放。

semaphore.acquire(2)表示至少需要2个授权才可以放行代码。此时可以调用2次无参方法semaphore.release()或者直接一次性发放2个授权,调用semaphore.release(2)。
 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多