关于信号量

信号量(Semaphore)是Java多线程并发中的一种JDK内置同步器,通过它可以实现多线程对公共资源的并发访问控制。一个线程在进入公共资源时需要先获取一个许可,如果获取不到许可则要等待其它线程释放许可,每个线程在离开公共资源时都会释放许可。它包含了非公平模式和公平模式。

例子一

我们先看一个简单的例子,首先实例化一个拥有5个许可的信号量对象,然后一共有10个线程一同尝试获取5个许可,得到许可的线程将value进行累加1,接着睡眠五秒,最后释放许可。

public class SemaphoreDemo { static Semaphore semaphore = new Semaphore(5); static AtomicInteger value = new AtomicInteger(0); public static void main(String[] args) {  for (int i = 0; i  {    try {     semaphore.acquire();     System.out.println("counting number : " + value.incrementAndGet());     Thread.sleep(5000);     semaphore.release();    } catch (InterruptedException e) {    }   });   thread.start();  } }}

以上程序输出如下,其中有五个线程输出“counting number : xx”后其他线程则开始等待。大概等待5秒后获得许可的五个线程执行释放许可操作,然后其它线程才能获得许可并往下执行。

counting number : 2counting number : 1counting number : 5counting number : 3counting number : 4counting number : 6counting number : 7counting number : 8counting number : 9counting number : 10

非公平模式的实现

Semaphore类的实现是基于AQS同步器来实现的,不管是公平模式还是非公平模式都是基于AQS的共享模式,只是在获取许可的操作逻辑上有差异。Semaphore的默认模式为非公平模式,我们先看非公平模式的实现。




java信号量countdownlatch java信号量原理_实现原理


Semaphore的实现

Semaphore类的几个主要方法如下所示,其中提供了两个构造函数,相关的两个参数为许可最大数和是否使用公平模式,其中FairSync是公平模式的同步器而NonfairSync则是非公平模式的同步器。有两个acquire方法,无参时默认是一次获取1个许可,而如果传入整型参数则表示一次获取若干个许可。对应地,也有两个release方法,无参时表示释放1个许可,而整型参数则表示一次释放若干个许可。

public class Semaphore implements java.io.Serializable { private final Sync sync; public Semaphore(int permits) {  sync = new NonfairSync(permits); } public Semaphore(int permits, boolean fair) {  sync = fair ? new FairSync(permits) : new NonfairSync(permits); } public void acquire() throws InterruptedException {  sync.acquireSharedInterruptibly(1); } public void release() {  sync.releaseShared(1); } public void acquire(int permits) throws InterruptedException {  if (permits

Semaphore内部的Syn子类是公平模式FairSync类和非公平模式NonfairSync类的抽象父类,许可最大数与AQS同步器的状态变量对应。因为模式是非公平模式,所以这里提供了非公平的许可获取方法nonfairTryAcquireShared。非公平模式其实就是在许可数量允许的情况下,让所有线程都进行自旋操作,而不管它们先来后到的顺序,全部线程放到一起去竞争许可。其中compareAndSetState方法提供了CAS算法从而能够保证并发修改许可值,而剩余许可数等于当前可用许可值减去当前消耗许可数,需要注意的是当剩余许可数小于0时则返回负数从而导致线程会进入等待队列中。tryReleaseShared方法则提供了释放许可的操作,不管是不是公平模式都使用该方法即可,释放许可的逻辑是相同的。通过自旋操作来将释放的许可数增加到当前剩余许可数。

abstract static class Sync extends AbstractQueuedSynchronizer {  Sync(int permits) {   setState(permits);  }  final int getPermits() {   return getState();  }  final int nonfairTryAcquireShared(int acquires) {   for (;;) {    int available = getState();    int remaining = available - acquires;    if (remaining

非公平模式NonfairSync类的实现主要是tryAcquireShared方法,直接调用父类Sync的的nonfairTryAcquireShared方法即可。

static final class NonfairSync extends Sync {  NonfairSync(int permits) {   super(permits);  }  protected int tryAcquireShared(int acquires) {   return nonfairTryAcquireShared(acquires);  } }

公平模式的实现

公平模式与非公平模式的主要差异就在获取许可时的机制,非公平模式直接通过自旋操作让所有线程竞争许可,从而导致了非公平。而公平模式则通过队列来实现公平机制。它们的差异就在tryAcquireShared方法,我们看公平模式的tryAcquireShared方法。实际上不同的地方就两行代码,它会检查是否已经存在等待队列,如果已经有等待队列则返回-1,返回-1则表示让AQS同步器将当前线程进入等待队列中,队列则意味着公平。实际上,这也并非是严格的公平,在前面讲到的AQS同步器的公平性章节有深入讲过AQS的公平性,如果忘记了可以重新查阅加深理解。而且在为达到最大许可数的情况下,所有线程也并没有进入等待队列中,而是全部线程进行自旋获取许可。

static final class FairSync extends Sync {  FairSync(int permits) {   super(permits);  }  protected int tryAcquireShared(int acquires) {   for (;;) {    if (hasQueuedPredecessors())     return -1;    int available = getState();    int remaining = available - acquires;    if (remaining