信号量

时间:2020-01-09 10:35:52  来源:igfitidea点击:

信号量是一种线程同步构造,可用于在线程之间发送信号以避免丢失信号,或者像使用锁一样保护关键部分。 Java 5带有java.util.concurrent包中的信号量实现,因此我们不必实现自己的信号量。尽管如此,了解其实现和使用背后的理论还是很有用的。

Java 5带有内置的"信号量",因此我们不必自己实现。

简单信号量

这是一个简单的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()。如果对take()的调用发生在对release()的调用之前,则调用release()的线程仍将知道调用了take(),因为信号内部存储在signal中。多变的。 " 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...
    }
  }
}

计数信号量

上一节中的"信号量"实现不计算" 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--;
  }

}

有界信号量

" 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()方法现在将如何阻塞。如果" BoundedSemaphore"已达到其信号上限,则直到线程调用" release()"后,才允许调用" take()"的线程传递其信号。

将信号量用作锁

可以将有界信号量用作锁。为此,将上限设置为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个线程的线程操作不会冲突,否则应用程序将失败。

从finally块内部调用relase()方法,以确保即使从关键部分抛出异常,该方法也被调用。