官方解释:

  • 一个计数信号量。在概念上,信号量维持一组许可证。如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。每个release()添加许可证,潜在地释放阻塞获取方。但是,没有使用实际的许可证对象; Semaphore只保留可用数量的计数,并相应地执行。信号量通常用于限制线程数,而不是访问某些(物理或逻辑)资源

我记得考科目一的时候有一个大教室,这个教室只能同时允许两百人考试,当有一个考完之后,下一个才能进去进行考试。门口会有安检人员进行安检,这个Semaphore就相当于这个安检员。

也可以理解为停车场,停车场内的停车位是固定的,只有当一辆或多辆车开走之后外面等待的车才能进去停车。

用法:

1、定义三个资格
Semaphore semaphore = new Semaphore(3); ThreadPoolExecutor poolExecutor =
new ThreadPoolExecutor(10, 20,
5000, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<>(100)); for (int i = 0; i < 10; i++) {
int finalI = i;
poolExecutor.execute(new Thread() {
@Override
public void run() {
try {
//获取执行资格
semaphore.acquire(1);
System.out.println(finalI+"=========");
//模拟每个线程运行的时间
Thread.sleep(1000);
//释放执行资格
semaphore.release(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
} poolExecutor.shutdown();

  

运行结果如下:(同一时刻只能运行三个线程。有点模糊,凑合看)

解析:

一、定义:

public Semaphore(int permits) {    sync = new NonfairSync(permits);}

  

Semaphroe底层也是用Sync类,默认是非公平的,也有公平的构造方法。

public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

  

定义的资格数其实是设置锁的状态值的(AQS之前已说过,维护锁状态值和线程等待队列)

abstract static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) {
setState(permits);
}
}

  

二、为什么能限制同时执行的线程数量呢?

这就是acquire方法的用处了

public void acquire(int permits) {    sync.acquireSharedInterruptibly(permits);}

  

点进acquireSharedInterruptibly这个方法看看:

public final void acquireSharedInterruptibly(int arg)
{
1、尝试获取锁,返回值小于0就是获取锁失败
if (tryAcquireShared(arg) < 0)
2、如果获取失败,则进入队列进行等待,之前已经解析过
doAcquireSharedInterruptibly(arg);
}

  

可以看到,跟之前CountDownLatch的await方法是一样的。

tryAcquireShared方法最终执行的如下方法:

final int nonfairTryAcquireShared(int acquires) {
for (;;) {
1、获取当前锁状态,锁状态值一开始是自定义的
int available = getState();
2、当前申请后剩余的锁状态值
int remaining = available - acquires;
if (3、如小于0,则申请失败,进入等待队列中
remaining < 0 ||
4、CAS替换锁状态值
compareAndSetState(available, remaining))
return remaining;
}
}

  

上述是非公平的,公平的只加了一个判断线程等待队列前是否有其它线程。排队一个一个来。

static final class FairSync extends Sync {

        protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
}

  

这个就是为什么Semaphore能控制当前并发线程的数量的原因。

三、释放锁

线程获取执行资格之后需要释放锁。这就是release方法的用处。不释放的话锁会一直被占用,其他线程就无法运行。

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

  

点进releaseShared看看

public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

  

跟之前的CountDownLatch是一样的,只是实现不一样。Semaphore实现如下:

protected final boolean tryReleaseShared(int releases) {
for (;;) {
1、获取锁当前状态
int current = getState();
2、释放锁,直接相加
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
3、用CAS更新锁状态
if (compareAndSetState(current, next))
return true;
}
}

  

=======================================================

我是Liusy,一个喜欢健身的程序员。

欢迎关注微信公众号【Liusy01】,一起交流Java技术及健身,获取更多干货,最新更新【K8S】。

Semaphore最详细解析的更多相关文章

  1. java类生命周期详细解析

    (一)详解java类的生命周期 引言 最近有位细心的朋友在阅读笔者的文章时,对java类的生命周期问题有一些疑惑,笔者打开百度搜了一下相关的问题,看到网上的资料很少有把这个问题讲明白的,主要是因为目前 ...

  2. springmvc 项目完整示例06 日志–log4j 参数详细解析 log4j如何配置

    Log4j由三个重要的组件构成: 日志信息的优先级 日志信息的输出目的地 日志信息的输出格式 日志信息的优先级从高到低有ERROR.WARN. INFO.DEBUG,分别用来指定这条日志信息的重要程度 ...

  3. include_path详细解析

    include_path详细解析     原文地址:http://www.laruence.com/2010/05/04/1450.html 1.php默认的包含路径为 .;C:\php\pear 即 ...

  4. Intent的详细解析以及用法

    Intent的详细解析以及用法      Android的四大组件分别为Activity .Service.BroadcastReceiver(广播接收器).ContentProvider(内容提供者 ...

  5. C++多态的实现及原理详细解析

    C++多态的实现及原理详细解析 作者: 字体:[增加 减小] 类型:转载   C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型 ...

  6. 对MySQL DELETE语法的详细解析

    以下的文章主要描述的是MySQL DELETE语法的详细解析,首先我们是从单表语法与多表语法的示例开始的,假如你对MySQL DELETE语法的相关内容十分感兴趣的话,你就可以浏览以下的文章对其有个更 ...

  7. 转:二十一、详细解析Java中抽象类和接口的区别

    转:二十一.详细解析Java中抽象类和接口的区别 http://blog.csdn.net/liujun13579/article/details/7737670 在Java语言中, abstract ...

  8. 单表扫描,MySQL索引选择不正确 并 详细解析OPTIMIZER_TRACE格式

    单表扫描,MySQL索引选择不正确 并 详细解析OPTIMIZER_TRACE格式     一 表结构如下:  万行 CREATE TABLE t_audit_operate_log (  Fid b ...

  9. 在PHP中使用CURL,“撩”服务器只需几行——php curl详细解析和常见大坑

    在PHP中使用CURL,"撩"服务器只需几行--php curl详细解析和常见大坑 七夕啦,作为开发,妹子没得撩就"撩"下服务器吧,妹子有得撩的同学那就左拥妹子 ...

随机推荐

  1. [bash] 获取linux主机名,检视内中是否有特定字符串

    代码: #!/bin/bash hostname=$(hostname) #调用hostname命令获取主机名放入变量hostname中 #echo $hostname if [ `echo ${ho ...

  2. java安全编码指南之:Number操作

    目录 简介 Number的范围 区分位运算和算数运算 注意不要使用0作为除数 兼容C++的无符号整数类型 NAN和INFINITY 不要使用float或者double作为循环的计数器 BigDecim ...

  3. latex 封面

    latex 封面 代码: \begin{titlepage} \heiti \vspace*{64pt} \begin{center} \fontsize{48pt}{0} XX大学\\ \vspac ...

  4. Table内部实现2

    这一节介绍Lua唯一的数据结构table,相对于大部分语言提供数组和字典两种类型,Lua将其合二为一,颇为精巧的实现了table. table充分体现了Lua语言的特点,用最简练的语法表达丰富的信息, ...

  5. PHP7做了哪些优化

    一  zval使用栈内存 在Zend引擎和扩展中,经常要创建一个PHP的变量,底层就是一个zval指针.之前的版本都是通过MAKE_STD_ZVAL动态的从堆上分配一个zval内存.而PHP7可以直接 ...

  6. Docker端口映射及创建镜像演示(二)

    Docker暴露容器方法 第一种:将容器中的一个端口映射成宿主机中的一个随机端口 第二种:将容器中的一个端口映射成宿主机中的一个端口 第三种:将容器中的一个端口映射成宿主机中的一个特定网卡上的随机端口 ...

  7. apche—LAMP(一)

    Apache HTTPD Server 简称 Apache,是 Apache 软件基金会的一个开源的网页服务器, 可以在大多数计算机操作系统中运行,由于其多平台和安全性被广泛使用,是最流行的 Web  ...

  8. Vue搭建组件库并发布到 npm

    https://www.jianshu.com/p/72d303449abc

  9. TS数据类型:类型别名/联合类型/字面量类型/类型推论等纲要

    在学C/C++  Java等强类型语言时,变量类型是唯一的,需要先指定.PHP JavaScript等弱类型语言时,无需指定变量类型 但是,TypeScript里面的联合类型 (Union Type) ...

  10. Analytics Zoo Cluster Serving自动扩展分布式推理

    作者: Jiaming Song, Dongjie Shi, Gong, Qiyuan, Lei Xia, Wei Du, Jason Dai 随着深度学习项目从实验到生产的发展,越来越多的应用需要对 ...