呵,Semaphore ,就这?(二)

简介: 那么本篇文章我们继续来和你聊聊并发工具类的第二篇文章 --- Semaphore 。

Semaphore 的公平性和非公平性

那么我们进入 Sync 内部看看它实现了哪些方法

abstract static class Sync extends AbstractQueuedSynchronizer {
  private static final long serialVersionUID = 1192457210091910933L;
  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 < 0 ||
          compareAndSetState(available, remaining))
        return remaining;
    }
  }
  protected final boolean tryReleaseShared(int releases) {
    for (;;) {
      int current = getState();
      int next = current + releases;
      if (next < current) // overflow
        throw new Error("Maximum permit count exceeded");
      if (compareAndSetState(current, next))
        return true;
    }
  }
  final void reducePermits(int reductions) {
    for (;;) {
      int current = getState();
      int next = current - reductions;
      if (next > current) // underflow
        throw new Error("Permit count underflow");
      if (compareAndSetState(current, next))
        return;
    }
  }
  final int drainPermits() {
    for (;;) {
      int current = getState();
      if (current == 0 || compareAndSetState(current, 0))
        return current;
    }
  }
}

首先是 Sync 的初始化,内部调用了 setState 并传递了 permits ,我们知道,AQS 中的 State 其实就是同步状态的值,而 Semaphore 的这个 permits 就是代表了许可的数量。

getPermits 其实就是调用了 getState 方法获取了一下线程同步状态值。后面的 nonfairTryAcquireShared 方法其实是在 Semaphore 中构造了 NonfairSync 中的 tryAcquireShared 调用的

微信图片_20220418200248.png

这里需要提及一下什么是 NonfairSync,除了 NonfairSync 是不是还有 FairSync 呢?查阅 JDK 源码发现确实有。

那么这里的 FairSync 和 NonfairSync 都代表了什么?为什么会有这两个类呢?

事实上,Semaphore 就像 ReentrantLock 一样,也存在“公平”和"不公平"两种,默认情况下 Semaphore 是一种不公平的信号量

微信图片_20220418200252.png

Semaphore 的不公平意味着它不会保证线程获得许可的顺序,Semaphore 会在线程等待之前为调用 acquire 的线程分配一个许可,拥有这个许可的线程会自动将自己置于线程等待队列的头部。

当这个参数为 true 时,Semaphore 确保任何调用 acquire 的方法,都会按照先入先出的顺序来获取许可。

final int nonfairTryAcquireShared(int acquires) {

for (;;) {
// 获取同步状态值
int available = getState();
// state 的值 - 当前线程需要获取的信号量(通常默认是 -1),只有
// remaining > 0 才表示可以获取。
int remaining = available - acquires;
// 先判断是否小于 0 ,如果小于 0 则表示无法获取,如果是正数
// 就需要使用 CAS 判断内存值和同步状态值是否一致,然后更新为同步状态值 - 1
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}

微信图片_20220418200257.png

从上面这幅源码对比图可以看到,NonfairSync 和 FairSync 最大的区别就在于 tryAcquireShared 方法的区别。

NonfairSync 版本中,是不会管当前等待队列中是否有排队许可的,它会直接判断信号许可量和 CAS 方法的可行性。

FairSync 版本中,它首先会判断是否有许可进行排队,如果有的话就直接获取失败。

这时候可能就会有读者问了,你上面说公平性和非公平性的区别一直针对的是 acquire 方法来说的,怎么现在他们两个主要的区别在于 tryAcquireShared 方法呢?

别急,让我们进入到 acquire 方法一探究竟

微信图片_20220418200303.png

可以看到,在 acquire 方法中,会调用 tryAcquireShared 方法,根据其返回值判断是否调用 doAcquireSharedInterruptibly 方法,更多关于 doAcquireSharedInterruptibly 的使用分析,请参考读者的这篇文章

一文搞懂 CountDownLatch 用法和源码!

这里需要注意下,acquire 方法具有阻塞性,而 tryAcquire 方法不具有阻塞性。

这也就是说,调用 acquire 方法如果获取不到许可,那么 Semaphore 会阻塞,直到有可用的许可。而 tryAcquire 方法如果获取不到许可会直接返回 false。

这里还需要注意下 acquireUninterruptibly 方法,其他 acquire 的相关方法要么是非阻塞,要么是阻塞可中断,而 acquireUninterruptibly 方法不仅在没有许可的情况下执着的等待,而且也不会中断,使用这个方法时需要注意,这个方法很容易在出现大规模线程阻塞而导致 Java 进程出现假死的情况。

有获取许可相对应的就有释放许可,但是释放许可不会区分到底是公平释放还是非公平释放。不管方式如何都是释放一个许可给 Semaphore ,同样的 Semaphore 中的许可数量会增加。

微信图片_20220418200308.png

在上图中调用 tryReleaseShared 判断是否能进行释放后,再会调用 AQS 中的 releasedShared 方法进行释放。

微信图片_20220418200311.png

上面这个释放流程只是释放一个许可,除此之外,还可以释放多个许可

public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}

后面这个 releaseShared 的释放流程和上面的释放流程一致。

其他 Semaphore 方法

除了上面基本的 acquire 和 release 相关方法外,我们也要了解一下 Semaphore 的其他方法。Semaphore 的其他方法比较少,只有下面这几个

drainPermits :获取并退还所有立即可用的许可,其实相当于使用 CAS 方法把内存值置为 0

reducePermits:和 nonfairTryAcquireShared 方法类似,只不过 nonfairTryAcquireShared 是使用 CAS 使内存值 + 1,而 reducePermits 是使内存值 - 1 。

isFair:对 Semaphore 许可的争夺是采用公平还是非公平的方式,对应到内部的实现就是 FairSync 和 NonfairSync。

hasQueuedThreads:当前是否有线程由于要获取 Semaphore 许可而进入阻塞。

getQueuedThreads:返回一个包含了等待获取许可的线程集合。

getQueueLength:获取正在排队而进入阻塞状态的线程个数。

            </div>
目录
相关文章
|
2月前
|
Java
JAVA并发编程系列(7)Semaphore信号量剖析
腾讯T2面试,要求在3分钟内用不超过20行代码模拟地铁安检进站过程。题目设定10个安检口,100人排队,每人安检需5秒。实际中,这种题目主要考察并发编程能力,特别是多个线程如何共享有限资源。今天我们使用信号量(Semaphore)实现,限制同时进站的人数,并通过信号量控制排队和进站流程。并详细剖析信号量核心原理和源码。
Semaphore 使用详解
本文主要对Semaphore 的相关知识点进行了介绍和讲解
105 0
|
6月前
|
数据库连接
认识 Semaphore
认识 Semaphore
48 0
|
Java 数据库连接 API
【JUC】信号量Semaphore详解
【JUC】信号量Semaphore详解
171 0
【JUC】信号量Semaphore详解
|
监控 数据库连接
呵,Semaphore ,就这?(一)
那么本篇文章我们继续来和你聊聊并发工具类的第二篇文章 --- Semaphore 。
125 0
呵,Semaphore ,就这?(一)
|
Java
呵,Semaphore ,就这?(二)
那么本篇文章我们继续来和你聊聊并发工具类的第二篇文章 --- Semaphore 。
124 0
呵,Semaphore ,就这?(二)
并发编程之Semaphore信号量
`Semaphore` 翻译过来就是信号量, 其根本原理就是基于 `CAS` 共享锁的一种实现。举一个例子。 假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆不受阻碍的进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入一辆,如果又离开两辆,则又可以放入两辆,如此往复。
164 0
|
Java
Java并发编程之Semaphore信号量
Java并发编程之Semaphore信号量
151 0
Java并发编程之Semaphore信号量
Semaphore信号量
Semaphore 可以用来限制或管理数量有限资源的使用情况 - 信号量的作用是用来维护一个“许可证”,的计数,线程可以获取 许可证,那信号量剩余许可证就减一,线程也可以是否一个许可证,那剩余的许可证就加一,当信号量拥有的许可证为0时,那么下一个线程想获得许可证,就要进行等待,直到另外线程释放许可证
276 0
Semaphore信号量