Semaphore 是 Java 并发包 (java.util.concurrent) 中的重要工具,主要用于控制多线程对共享资源的并发访问量。它可以设置“许可证”(permit)的数量,并允许指定数量的线程同时访问某一资源,适合限流、资源池等场景。下面从源码设计、底层原理、应用场景、以及与其它 JUC 工具的对比来详细剖析 Semaphore。

一、Semaphore 的基本原理

Semaphore 本质上是一种计数信号量,内部维护一个许可计数,每个线程在进入时需要申请一个许可(acquire),完成后释放该许可(release)。当许可计数为零时,其他线程会阻塞,直到有线程释放许可。

1. 计数和许可

  • 许可数:Semaphore 在初始化时设置许可数,通常表示可以同时访问资源的线程数量。
  • 计数增减:调用 acquire() 时减少许可数,调用 release() 时增加许可数。
  • 公平性:Semaphore 可以设置为“公平”模式,保证线程按 FIFO 顺序获得许可。默认是“非公平”模式,性能更高,但线程获取许可的顺序不保证。

二、底层实现与源码解析

Semaphore 基于 AbstractQueuedSynchronizer (AQS) 实现,其实现方式和 CountDownLatch 类似,但使用了 AQS 的共享模式,并对许可计数进行精确管理。

1. AQS 的共享模式

Semaphore 使用 AQS 的共享模式(Shared),其中 AQS 的 state 表示剩余许可数。acquireShared() 方法用于申请许可,releaseShared() 方法用于释放许可。

2. 内部类 Sync 的实现

Semaphore 的核心实现依赖于其内部类 Sync,Sync 是 AbstractQueuedSynchronizer 的子类。根据是否是公平模式,有两种实现:NonfairSyncFairSync

  • NonfairSync:非公平模式。线程调用 acquire 时直接尝试获取许可,不保证顺序。
  • FairSync:公平模式。线程获取许可遵循队列顺序。
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 < 0 || compareAndSetState(available, remaining))
return remaining;
}
} protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (compareAndSetState(current, next))
return true;
}
}
}
  • 非公平模式(nonfairTryAcquireShared:直接尝试获取许可,如果成功则更新 state
  • 公平模式(FairSync:遵循 AQS 的队列顺序,确保 FIFO 访问。

三、Semaphore 的使用方法

Semaphore 提供了三种主要方法来操作许可:

  • acquire():获取一个许可,若没有许可则阻塞。
  • release():释放一个许可,唤醒等待的线程。
  • tryAcquire():尝试获取许可,但不阻塞。
Semaphore semaphore = new Semaphore(3); // 初始许可数为 3

// 获取许可,若无可用许可则阻塞
semaphore.acquire(); // 释放许可
semaphore.release(); // 尝试获取许可,若不可用立即返回 false,不会阻塞
if (semaphore.tryAcquire()) {
// do something
}

acquire() 源码

public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}

acquireSharedInterruptibly(1) 会调用 nonfairTryAcquireShared(1)tryAcquireShared(公平模式),尝试获取许可,若没有足够许可则阻塞等待。

release() 源码

public void release() {
sync.releaseShared(1);
}

releaseShared(1) 增加许可数并唤醒阻塞线程,使等待线程得以继续执行。

四、Semaphore 的应用场景

Semaphore 非常适合控制对有限资源的访问,典型的应用场景有:

1. 连接池限流

在数据库连接池中,可以使用 Semaphore 限制同时访问连接的线程数。例如,数据库连接数有限,通过 Semaphore 控制同时访问的线程数量,避免过度负载。

public class DatabaseConnectionPool {
private static final int MAX_CONNECTIONS = 5;
private final Semaphore semaphore = new Semaphore(MAX_CONNECTIONS); public Connection getConnection() throws InterruptedException {
semaphore.acquire();
return acquireConnection();
} public void releaseConnection(Connection conn) {
releaseConnection(conn);
semaphore.release();
}
}

2. 控制线程并发数

在高并发任务中,Semaphore 可限制同时执行的线程数量,例如,控制下载任务的并发数,避免过多线程导致的系统负载过高。

3. 资源分配和限流

Semaphore 适合资源共享场景,如共享打印机、固定线程数的线程池等,确保资源不会被过度占用。

五、与其他 JUC 工具的对比

1. CountDownLatch

  • 许可机制:Semaphore 的许可数可以动态变化,而 CountDownLatch 的计数器只能递减。
  • 适用场景:Semaphore 控制并发资源访问数量,而 CountDownLatch 用于线程等待。

2. CyclicBarrier

  • 作用:Semaphore 适合控制资源访问并发数,而 CyclicBarrier 则用于线程间的同步点,使所有线程达到某个屏障时一起继续。
  • 灵活性:Semaphore 更灵活,可随时 release(),不必等待所有线程。

3. ReentrantLock

  • 模式:ReentrantLock 是排他锁,每次只允许一个线程访问。Semaphore 允许多个线程共享资源(多个许可)。
  • 适用场景:ReentrantLock 适合独占资源场景,Semaphore 适合资源池、限流等。

4. Phaser

  • 复杂度:Phaser 用于复杂的多阶段同步,可以动态增加/减少线程,而 Semaphore 主要用于固定数量资源控制。
  • 适用性:Phaser 适合分阶段任务同步,而 Semaphore 适合控制资源并发数。

庐山烟雨浙江潮,未至千般恨不消。

到得还来别无事,庐山烟雨浙江潮。

近半年不会再更新了,居家办公的日子要结束了。读书,世界就在眼前;不读书,眼前就是世界。与你共勉!

一文彻底弄懂JUC工具包的Semaphore的更多相关文章

  1. 一文彻底弄懂cookie、session、token

    前言 作为一个JAVA开发,之前有好几次出去面试,面试官都问我,JAVAWeb掌握的怎么样,我当时就不知道怎么回答,Web,日常开发中用的是什么?今天我们来说说JAVAWeb最应该掌握的三个内容. 发 ...

  2. 一文彻底弄懂this关键字用法

    哈喽,大家好,我是指北君. 介绍完 native.static.final 关键字后,指北君再接再厉,接着为大家介绍另一个常用的关键字--this. this 也是Java中的一个关键字,在<J ...

  3. 一文弄懂神经网络中的反向传播法——BackPropagation【转】

    本文转载自:https://www.cnblogs.com/charlotte77/p/5629865.html 一文弄懂神经网络中的反向传播法——BackPropagation   最近在看深度学习 ...

  4. 一文弄懂-Netty核心功能及线程模型

    目录 一. Netty是什么? 二. Netty 的使用场景 三. Netty通讯示例 1. Netty的maven依赖 2. 服务端代码 3. 客户端代码 四. Netty线程模型 五. Netty ...

  5. 一文弄懂-《Scalable IO In Java》

    目录 一. <Scalable IO In Java> 是什么? 二. IO架构的演变历程 1. Classic Service Designs 经典服务模型 2. Event-drive ...

  6. 一文弄懂-BIO,NIO,AIO

    目录 一文弄懂-BIO,NIO,AIO 1. BIO: 同步阻塞IO模型 2. NIO: 同步非阻塞IO模型(多路复用) 3.Epoll函数详解 4.Redis线程模型 5. AIO: 异步非阻塞IO ...

  7. 一文弄懂CGAffineTransform和CTM

    一文弄懂CGAffineTransform和CTM 一些概念 坐标空间(系):视图(View)坐标空间与绘制(draw)坐标空间 CTM:全称current transformation matrix ...

  8. 【TensorFlow】一文弄懂CNN中的padding参数

    在深度学习的图像识别领域中,我们经常使用卷积神经网络CNN来对图像进行特征提取,当我们使用TensorFlow搭建自己的CNN时,一般会使用TensorFlow中的卷积函数和池化函数来对图像进行卷积和 ...

  9. 一文带你弄懂 JVM 三色标记算法!

    大家好,我是树哥. 最近和一个朋友聊天,他问了我 JVM 的三色标记算法.我脑袋一愣发现竟然完全不知道!于是我带着疑问去网上看了几天的资料,终于搞清楚啥事三色标记算法,它是用来干嘛的,以及它和 CMS ...

  10. 一文带你弄懂 Maven 拉包原理

    业务需求开发的时候,我们总是会遇到拉不到依赖包的情况.此时如果不清楚 Maven 拉取依赖包的原理,那么很可能找不到问题所在.今天树哥就带大家了解下 Maven 拉包的原理,让你在遇到问题的时候能快速 ...

随机推荐

  1. 20.从0学ARM-移植uboot支持exynos4412

    经过前面一章的学习,我们已经了解了什么是uboot,本章主要目的是如何编译和移植uboot,增加串口.网络.emmc等功能,让他支持exynos4412开发板. 一.移植步骤 1. Uboot配置 指 ...

  2. mariadbl数据库环境迁移

    前言: 数据库的主要作用就是对数据进行保存和维护,所以备份数据是数据库管理中最常用的操作.为了防止数据库意外崩溃或硬件损伤而导致的数据丢失,数据库系统提供了备份和恢复策略.保证数据安全的最重要的一个措 ...

  3. Mac 使用 Caps Lock 键切换输入法失灵问题解决

    Mac 上的 Caps Lock 键对于多语言用户来说,除了切换输入大小写的作用外还承担着切换输入法的功能.正常情况下,轻按一下 Caps Lock 键是切换输入法,长按是切换输入大小写.然而有时这个 ...

  4. 生产级Redis 高并发分布式锁实战2:缓存架构设计问题优化

    对于大多数高并发场景,都是读多写少.比如商品信息,医生挂号信息等.提交订单页只有一个操作. 对于一个普通的缓存架构设计,实现商品的增删改查功能,代码如下: Controller 层 @RestCont ...

  5. Asp.net Core 学习笔记 Azure Storage

    更新: 2021-07-22 使用 Azure storage 以后, 还要解决一个 url 的问题. 文件自然是通过我们的 domain 来访问才合理丫. 这个是 azure 的 url : htt ...

  6. 强!推荐一款Python开源自动化脚本工具:AutoKey!

    1.前言 在数字化时代,自动化工具成为了提升工作效率和生产力的重要手段.Python作为一种广泛使用的编程语言,以其强大的功能和易用性受到许多开发者的青睐. 而今天给大家推荐一款开源的自动化脚本工具: ...

  7. @vue/cli eslint插件使用指南

    使用步骤 使用 npm 安装 @vue/cli-service 版本对应的 @vue/cli-plugin-eslint 例如:"@vue/cli-service": " ...

  8. MoNA:复用跨模态预训练模型,少样本模态的福音 | ICML'24

    跨模态转移旨在利用大型预训练模型来完成可能不属于预训练数据模态的任务.现有的研究在将经典微调扩展到跨模态场景方面取得了一定的成功,但仍然缺乏对模态差距对转移的影响的理解.在这项工作中,进行了一系列关于 ...

  9. Spring事务传播机制(最全示例)

    我们在使用Spring框架进行开发时,经常在service层写很多方法,而且这些方法都是带事务的,那么Spring的事务怎么在多个方法之间传播呢?今天我们就仔细聊一聊. Spring的事务传播机制主要 ...

  10. 将Sublime Text添加到鼠标右键

     事件起因: 在众多 文本文档软件中,个人用过比较多,notepad++ / Edit / EditPlus / notepad--  / Sublime Text 等等,但是比较偏爱的还是 Subl ...