`
manzhizhen
  • 浏览: 295470 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

信号量Semaphore初探

    博客分类:
  • Java
阅读更多

         信号量Semaphorejava.util.concurrent包下一个常用的同步工具类,它维护了一个许可集,可以理解成资源数,可以通过acquire操作来获取一个资源,并通过release来释放一个资源,但需要注意的是,release来释放资源前不一定要先通过acquire来获取一个资源,如果不断的release而不进行acquire将导致资源数虚增,所以一定得我们自己来保证使用的正确性。

         我们经常用信号量来管理可重复使用的资源,比如数据库连接、线程等,因为这些资源都有着可预估的上限,所以我们在初始化Semaphore时设定的许可数和我们需要管理的资源数一致,获取一个资源时就通过acquire来获取一个许可,如果没有可用资源,则acquire将阻塞,释放一个资源时,通过release来释放一个许可。用Semaphore来限制操作的并发访问程度也类似。

        但有一点需要注意,Semaphore没有直接提供更新许可总数的方法,虽然你可以单独通过acquirerelease来特意的减少或增加许可的总量,但这样做会让人感到奇怪。那么为什么Semaphore没有单独同重设信号量数量的方法呢?直接把AQSsetState方法暴露出来不就行的吗?因为setState操作如果发生在在某些使用该Semaphore的线程还没有走完整个信号量的获取和释放的流程时,将会直接导致state值的不准确。现在说可能让人不太理解,不用担心,看完本文,你就懂了。有人会想到当需要修改许可总数时,我再重新new一个Semaphore出来不就行了?比如像下面:

private volatile Semaphore jdbcConnection = new Semaphore(10);

public void resetJdbcConnection(int jdbcConnectionAmount) {

    jdbcConnection = new Semaphore(jdbcConnectionAmount);

}

这里通过提供resetJdbcConnection方法来让外部可以修改jdbcConnection的许可数目,注意,这里的jdbcConnection必须是volatile这样暴力修改是有风险的,因为你在修改时jdbcConnection很可能正在被使用,比如它进行了jdbcConnection.acquire()操作后,你把jdbcConnection给更换成另一个新的Semaphore,所以在你使用jdbcConnection.release()来释放一个许可时,是在新的Semaphore进行release操作,所以新的Semaphore的许可数量被莫名的+1了。有一种解决办法是用局部变量记录下操作acquire时的Semaphore,并在进行release时使用该局部变量来进行release,这样保证了acquirerelease是在同一个Semaphore上操作,这种方法简单有效,适合绝大多数场景。之所以说Semaphore没有直接提供更新许可总数的方法,是因为Semaphore确有一个永久减少许可总数的方法,即:

protected void reducePermits(int reduction) {

    if (reduction < 0) throw new IllegalArgumentException();

    sync.reducePermits(reduction);

}

很显然,它需要子类来继承使用,别妄想将reduction传入负数来使许可数增加,因为sync.reducePermits(reduction);中对reduction的值做了限制:

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;

    }

}

有人会好奇,为啥Semaphore提供了减少许可数的入口,但未提供增加许可数的入口?这个我暂时还未找到原因。

 

    现在我们来分析Semaphore的内部实现,像绝大多数并发工具类一样,Semaphore也依赖于AQSAbstractQueuedSynchronizer),关于AQS的简单分析,可以参考我的另一篇博文:http://manzhizhen.iteye.com/blog/2305890。你会很自然的想到,初始化时许可的总数就是保存在AQSstate属性中的:

Sync(int permits) {

    setState(permits);

}

 

final int getPermits() {

    return getState();

}

SyncSemaphore中实现的AQS的内部类,我们现在来看下Sync中关于非公平获取信号量和释放信号量的两个方法的实现:

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;

    }

}

nonfairTryAcquireShared方法中可以看出,当需要获取acquires数量的许可时,先看可用的许可够不够,如果不够(remaining < 0),则直接返回还差的许可数的负数值,如果够,则从可用许可数中减去acquires,并返回剩余可用的许可数。由于nonfairTryAcquireShared是非阻塞的,所以它直接在Semaphore中的tryAcquire非阻塞方法中使用:

public boolean tryAcquire() {

    return sync.nonfairTryAcquireShared(1) >= 0;

}

public boolean tryAcquire(int permits) {

    if (permits < 0) throw new IllegalArgumentException();

    return sync.nonfairTryAcquireShared(permits) >= 0;

}

 

可见,对于tryAcquire的非阻塞方法,Semaphore的公平模式和非公平模式下的实现都是一样的,它在能获取到足够许可时不需要进入队列而是直接拿到走人,不能获取足够许可时就直接返回,是非公平的!注意,对于tryAcquire的阻塞方法还是有公平和非公平之分的。tryReleaseShared是用来释放许可数,我们可以看内部实现也很简单,就是不断的进行CAS操作指导成功将releases数目的许可数加回到当前可用许可数中。前面已经说过,Seamphore无法去校验你获取许可和释放许可是否一一对应,因为获取和释放都是直接在AQSstate上操作的,所以操作一段时间后,连AQS自己都忘记最初的state值是啥了,所以当然无法在中途来校验获取和释放是否正确,即使知道state的初始值,也很难在交错的获取和释放许可的操作过程中做一致性检查。

        看过SemaphoreAPI文档都知道Semaphore中由公平(fair)和非公平(nonfair)两种模式,这两种模式在Semaphore创建时确定,中途不能修改,默认是非公平的。Semaphore的构造函数如下:

public Semaphore(int permits, boolean fair) {

    sync = fair ? new FairSync(permits) : new NonfairSync(permits);

}

从名字就可以看出,FairSync代表公平的Sync实现,而NonfairSync代表非公平的Sync实现,从名字也看出内部类FairSyncNonfairSync都继承自上面提到的抽象类Sync那么公平和非公平的含义到底是什么呢?看过我讲述AQS博文的朋友应该知道,我说过,只要线程由于获取资源数失败而进入队列中后,就一定得等前面的节点获取完锁才有机会尝试获取锁,也就是说在AQS队列中的线程绝对是公平的,因为队列本来就是先进先出,即先到先得。但如果你想获取的资源数现在就有,那么即使现在队列中有线程排队在等,你也可以不用进入队列而直接拿到你想要的资源,这就是非公平!所以,你很自然的想到,公平的做法就是不管此时有没有可用的许可,只要队列中有线程在等,你就得给我乖乖去排队,没错,公平就是这样的!我们先看非公平的NonfairSync实现:

static final class NonfairSync extends Sync {

    private static final long serialVersionUID = -2694183684443567898L;

 

    NonfairSync(int permits) {

        super(permits);

    }

 

    protected int tryAcquireShared(int acquires) {

        return nonfairTryAcquireShared(acquires);

    }

}

tryAcquireShared方法调用的nonfairTryAcquireShared是在父类Sync中实现的,前面已经给出了代码实现,这里就不在多说了。我们再看看公平的FairSync实现:

static final class FairSync extends Sync {

    private static final long serialVersionUID = 2014338818796000944L;

 

    FairSync(int permits) {

        super(permits);

    }

 

    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;

        }

    }

}

FairSync的获取许可数方法tryAcquireSharednonfairTryAcquireShared比起来唯一的特别之处就是hasQueuedPredecessors方法的调用,而hasQueuedPredecessorsAQS中实现的:

public final boolean hasQueuedPredecessors() {

    // The correctness of this depends on head being initialized

    // before tail and on head.next being accurate if the current

    // thread is first in queue.

    Node t = tail; // Read fields in reverse initialization order

    Node h = head;

    Node s;

    return h != t &&

        ((s = h.next) == null || s.thread != Thread.currentThread());

}

从方法名和实现可以看出,hasQueuedPredecessors主要用来判断AQS队列中是否还有等待的线程节点,如果有,并且不是当前的线程节点,则返回true,所以,对于tryAcquireShared来说,此时都不进行资源获取的尝试就直接返回-1表明资源获取失败了。

 

现在许可数、公平和非公平都介绍完了,我们来看看获取许可数的相关方法。acquire将会在获取许可之前一直阻塞,或者被中断,我们看其内部实现:

public void acquire() throws InterruptedException {

    sync.acquireSharedInterruptibly(1);

}

 

public void acquire(int permits) throws InterruptedException {

    if (permits < 0) throw new IllegalArgumentException();

    sync.acquireSharedInterruptibly(permits);

}

 可以看出,它直接调用了AQS中的acquireSharedInterruptibly的实现,acquireSharedInterruptibly的实现在我的AQS博文中已经分析过,这里不再阐述。我们再来看看获取信号的限时阻塞方法tryAcquire的实现:

public boolean tryAcquire(long timeout, TimeUnit unit)

    throws InterruptedException {

    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));

}

public boolean tryAcquire(int permits, long timeout, TimeUnit unit)

    throws InterruptedException {

    if (permits < 0) throw new IllegalArgumentException();

    return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));

}

可见,Semaphore也没对其有特别的实现,而是直接调用AQS中的tryAcquireSharedNanos方法,tryAcquireSharedNanos在我的AQS博文中已经做过简单的介绍,这里不再阐述。

 

       可以看出,只要能弄懂AQS,你就可以明白Semaphore的核心实现!

分享到:
评论

相关推荐

    Android 线程池、信号量、Looper、缓存初探

    接着是信号量(Semaphore)。信号量是一种同步原语,用于控制对共享资源的访问。在多线程环境中,当多个线程尝试同时访问有限资源时,信号量可以限制同时访问的线程数量。在我们的图片加载示例中,如果同时有多个...

    c++多线程编程初探

    为了防止这种情况发生,我们可以使用互斥量(`Mutex`)、信号量(`Semaphore`)或临界区(`CriticalSection`)等机制来进行线程同步。 #### 四、结论 通过本文的介绍,我们可以看到C++多线程编程的基本概念和技术...

    Python多线程机制初探.zip

    - **`Semaphore`和`BoundedSemaphore`**:信号量用于限制同时访问特定资源的线程数量,防止资源过载。 6. **线程的生命周期** - **线程的生命周期包括创建、就绪、运行、阻塞和终止五个状态。` - **线程结束**:...

    9.docx An Introduction to Kernel Synchronization

    2. **信号量**(Semaphore):信号量不仅提供了互斥访问,还支持资源的数量限制。持有信号量的线程可以在必要时睡眠等待,待资源可用时再唤醒。 3. **读写锁**(Read-Write Locks):这种锁允许多个读取者同时访问...

    基于主从博弈的共享储能与综合能源微网优化运行研究:MATLAB与CPLEX实现

    内容概要:本文详细探讨了在主从博弈框架下,共享储能与综合能源微网的优化运行及其仿真复现。通过MATLAB和CPLEX的联合使用,展示了微网运营商和用户聚合商之间的动态博弈过程。上层模型关注微网运营商的定价策略,旨在最大化利润,考虑售电收益、储能运维成本等因素。下层模型则聚焦于用户聚合商的响应,根据电价调整电热负荷并参与共享储能调度。文中还介绍了电热耦合约束、充放电互斥约束等关键技术细节,并通过迭代博弈实现了策略更新。最终仿真结果显示,在引入电制热设备后,用户侧热负荷弹性提升,博弈收敛速度加快,达到双赢效果。 适合人群:从事能源系统优化、博弈论应用、MATLAB编程的研究人员和技术人员。 使用场景及目标:适用于希望深入了解主从博弈在综合能源系统中应用的学者和工程师。目标是掌握如何通过数学建模和编程实现复杂的能源系统优化,理解电热耦合机制和共享储能的作用。 其他说明:文章提供了详细的代码片段和仿真结果,帮助读者更好地理解和复现实验。此外,还讨论了一些常见的调试问题和解决方案,如约束冲突等。

    【基于矢量射线的衍射积分 (VRBDI)】基于矢量射线的衍射积分 (VRBDI) 和仿真工具附Matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    【深度学习应用综述】多领域关键技术及应用场景汇总:从计算机视觉到金融风控的全面解析

    内容概要:深度学习在多个领域有着广泛应用。在计算机视觉方面,涵盖图像分类、目标检测、图像分割等任务,应用于自动驾驶、医疗影像分析等领域;在自然语言处理上,包括机器翻译、文本分类、文本生成等功能,服务于信息检索、内容创作等;语音识别与合成领域,实现了语音到文本的转换以及文本到语音的生成,推动了智能交互的发展;医疗领域,深度学习助力医学影像分析、疾病预测、个性化治疗及健康监测;金融领域,深度学习用于信用风险评估、欺诈检测、高频交易等,保障金融安全并优化投资策略;自动驾驶方面,环境感知与决策控制系统确保车辆安全行驶;娱乐与媒体领域,个性化推荐和内容生成提升了用户体验;工业与制造业中,质量检测和预测性维护提高了生产效率和产品质量。 适合人群:对深度学习及其应用感兴趣的初学者、研究人员以及相关领域的从业者。 使用场景及目标:帮助读者全面了解深度学习在不同行业的具体应用场景,明确各领域中深度学习解决的实际问题,为后续深入研究或项目实施提供方向指引。 其他说明:随着深度学习技术的持续进步,其应用范围也在不断扩大,文中提及的应用实例仅为当前主要成果展示,未来还有更多潜力待挖掘。

    【ARIMA-LSTM】合差分自回归移动平均方法-长短期记忆神经网络研究附Python代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    周梁伟-大模型在融合通信中的应用实践.pdf

    周梁伟-大模型在融合通信中的应用实践

    基于S7-200 PLC与组态王的花式喷泉控制系统设计及应用

    内容概要:本文详细介绍了利用西门子S7-200 PLC和组态王软件构建的一个花式喷泉控制系统的设计与实现。首先阐述了系统的硬件组成,包括三个环形喷泉组、七彩LED灯带以及功放喇叭等组件,并给出了详细的IO分配表。接着深入解析了关键的梯形图程序逻辑,如自动模式循环、灯光控制、喷泉舞步等部分的具体实现方法。此外,还分享了一些实际调试过程中遇到的问题及其解决方案,例如电源隔离、电磁干扰处理等。最后展示了组态王界面上生动有趣的动画效果设计思路。 适合人群:对PLC编程和工业自动化感兴趣的工程师和技术爱好者。 使用场景及目标:适用于需要设计类似互动娱乐设施的专业人士,旨在帮助他们掌握从硬件选型、程序编写到界面美化的完整流程,从而能够独立完成类似的工程项目。 其他说明:文中不仅提供了理论知识讲解,还包括了许多实践经验教训,对于初学者来说非常有价值。同时,作者还在系统中加入了一些趣味性的元素,如隐藏模式等,增加了项目的吸引力。

    基于COMSOL的电弧熔池多物理场耦合仿真技术详解

    内容概要:本文详细介绍了利用COMSOL进行电弧熔池多物理场耦合仿真的方法和技术要点。首先解释了电弧熔池的本质及其复杂性,然后依次讲解了几何建模、材料属性设置、求解器配置以及后处理等方面的具体步骤和注意事项。文中提供了大量实用的MATLAB、Java和Python代码片段,帮助用户更好地理解和应用相关技术。此外,作者分享了许多实践经验,如分阶段激活物理场、使用光滑过渡函数处理相变、优化网格划分等,强调了参数选择和边界条件设定的重要性。 适合人群:从事电弧熔池仿真研究的专业人士,尤其是有一定COMSOL使用经验的研究人员。 使用场景及目标:适用于需要精确模拟电弧熔池行为的研究项目,旨在提高仿真精度并减少计算时间。主要目标是掌握多物理场耦合仿真的关键技术,解决实际工程中遇到的问题。 其他说明:文章不仅提供了详细的理论指导,还包括许多实用的操作技巧和常见错误的解决方案,有助于读者快速上手并深入理解电弧熔池仿真的难点和重点。

    9f148310e17f2960fea3ff60af384a37_098bb292f553b9f4ff9c67367379fafd.png

    9f148310e17f2960fea3ff60af384a37_098bb292f553b9f4ff9c67367379fafd

    spring-ai-hanadb-store-1.0.0-M7.jar中文-英文对照文档.zip

    # 【spring-ai-hanadb-store-1.0.0-M7.jar中文-英文对照文档.zip】 中包含: 中文-英文对照文档:【spring-ai-hanadb-store-1.0.0-M7-javadoc-API文档-中文(简体)-英语-对照版.zip】 jar包下载地址:【spring-ai-hanadb-store-1.0.0-M7.jar下载地址(官方地址+国内镜像地址).txt】 Maven依赖:【spring-ai-hanadb-store-1.0.0-M7.jar Maven依赖信息(可用于项目pom.xml).txt】 Gradle依赖:【spring-ai-hanadb-store-1.0.0-M7.jar Gradle依赖信息(可用于项目build.gradle).txt】 源代码下载地址:【spring-ai-hanadb-store-1.0.0-M7-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: spring-ai-hanadb-store-1.0.0-M7.jar中文-英文对照文档.zip,java,spring-ai-hanadb-store-1.0.0-M7.jar,org.springframework.ai,spring-ai-hanadb-store,1.0.0-M7,org.springframework.ai.vectorstore.hanadb,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,springframework,spring,ai,hanadb,store,中文-英文对照API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压 【spring-ai-hanadb-store-1.0.0-M7.jar中文-英文

    azure-ai-openai-1.0.0-beta.7.jar中文文档.zip

    # 压缩文件中包含: 中文文档 jar包下载地址 Maven依赖 Gradle依赖 源代码下载地址 # 本文件关键字: jar中文文档.zip,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压最外层zip,再解压其中的zip包,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件;

    3dmax插件复制属性.ms

    3dmax插件

    单相全桥PWM整流双闭环控制系统:电压环PI与电流环PR控制策略及其应用

    内容概要:本文详细介绍了单相全桥PWM整流器采用双闭环控制策略的具体实现方法和技术要点。电压环采用PI控制器来稳定直流侧电压,电流环则使用PR控制器确保交流电流与电压同相位并实现单位功率因数。文中提供了详细的MATLAB、C和Python代码片段,解释了各个控制器的设计思路和参数整定方法。此外,文章还讨论了突加负载测试、电压前馈补偿、PWM生成以及硬件选型等方面的内容,强调了系统的稳定性和快速响应能力。 适合人群:从事电力电子、自动控制领域的工程师和技术人员,尤其是对PWM整流器和双闭环控制感兴趣的读者。 使用场景及目标:适用于需要精确控制直流电压和交流电流的应用场景,如工业电源、新能源发电等领域。目标是提高系统的电能质量和动态响应速度,确保在负载变化时仍能保持稳定的输出。 其他说明:文章不仅提供了理论分析,还包括了大量的实际测试数据和波形图,帮助读者更好地理解和掌握双闭环控制的实际效果。同时,文中提到的一些调试技巧和注意事项对于实际工程应用非常有价值。

    easyocr安装包和模型

    easyocr安装包和模型

    AC-DIMMER交流调光灯stm32.7z

    AC_DIMMER交流调光灯stm32.7z

    仲量联行-负责任的房地产:实现社会价值,赋能建筑环境,创造积极的环境和社会影响.pdf

    仲量联行-负责任的房地产:实现社会价值,赋能建筑环境,创造积极的环境和社会影响

Global site tag (gtag.js) - Google Analytics