Semaphore(信号量)源码解读与使用

简介: Semaphore(信号量)源码解读与使用

1. 前言

在前面的文章我们讲解了CountDownLatch倒计时器的基本使用,本节我们继续来学习Java多线程编程中的一个工具类Semaphore信号量。

2. 什么是Semaphore?

Semaphore 是一个在多线程环境中用于控制对共享资源的访问的同步器(synchronizer),它是 Java 5 中引入的 java.util.concurrent(JUC)包的一部分。Semaphore 维护了一个许可集,线程在执行前必须从 Semaphore 获取一个许可。如果没有许可可用,线程将阻塞等待,直到其他线程释放许可。可以用来限制数据库连接数、限制服务器可处理请求数等。

3. Semaphore源码解读

我们跟进信号量的源码中浏览一圈,发现其实它内部主要的方法就2个:

// 初始共享资源数量
final Semaphore semaphore = new Semaphore(5);
// 获取1个许可
semaphore.acquire();
// 释放1个许可
semaphore.release();


3.1 acquire():获取许可

 3.1 acquire():获取许可

跟进这个方法后,我们会发现其内部调用了AQS的一个final 方法acquireSharedInterruptibly(),

/**
 * 共享模式下获取许可证,获取成功则返回,失败则加入阻塞队列,挂起线程
 */
public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
      throw new InterruptedException();
        // 尝试获取许可证,arg为获取许可证个数,当可用许可证数减当前获取的许可证数结果小于0,则创建一个节点加入阻塞队列,挂起当前线程。
    if (tryAcquireShared(arg) < 0)
      doAcquireSharedInterruptibly(arg);
}


我们可以发现这个方法中又调用了tryAcquireShared(arg)方法,作为AQS中的钩子方法,这个方法的实现在Semaphore的两个静态内部类 FairSync(公平模式) 和 NonfairSync(非公平模式) 中。虽然这个方法在AQS中,但它作为钩子方法,最终的实现则回到了Semaphore的内部类中。


我们接下来继续查看在 FairSync(公平模式)中的tryAcquireShared方法

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;
            }
        }
    }


这里就是获取剩余的许可数值,然后计算扣减后的许可数值,如果扣减后的值小于0,就会直接返回扣减数(||短路或,前者满足就不用判断后者了)。否认就会执行CAS操作compareAndSetState(available, remaining)扣减许可数的值。


这里我们就可知,Semaphore信号量的许可数是存放在AQS中的state变量中的(AQS源码我后续应该也会发文讲解),然后通过CAS来修改state的值,保证了操作的原子性。


3.2 release():释放许可

同样跟入这个方法,里面用了AQS的releaseShared(),而在这个方法内也毫无疑问的用了tryReleaseShared(int arg)这个钩子方法,原理同上,不再冗释,需要注意的是释放共享锁(也就是增加许可数state的值)的同时也会唤醒同步队列中的一个线程。

// 释放一个许可证
public void release() {
    sync.releaseShared(1);
}
 
// 释放共享锁,同时会唤醒同步队列中的一个线程。
public final boolean releaseShared(int arg) {
    // 尝试释放共享锁(增加许可数)
    if (tryReleaseShared(arg)) {
      //唤醒同步队列中的一个线程
      doReleaseShared();
      return true;
    }
    return false;
}
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;
            }
      }
 

4. Semaphore的使用

OK,讲到这里,把信号量中主要的方法解释完了,我们来写一个小demo感受一下它的使用:

public class Test {
    private final Semaphore semaphore;
 
    /*构造一个令牌*/
    public Test(int acq){
        this.semaphore= new Semaphore(acq);
    }
    public void useSemaphore(){
        try {
            semaphore.acquire();
            // 使用资源
            System.out.println("资源使用 " + Thread.currentThread().getName());
            Thread.sleep(1000); // 模拟资源使用时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
            System.out.println("资源释放 " + Thread.currentThread().getName());
        }
    }
 
    public static void main(String[] args) {
        Test test = new Test(3);
        for (int i = 0; i < 5; i++) {
            new Thread(test::useSemaphore).start();
        }
    }
}

输出:

资源使用 Thread-0
资源使用 Thread-1
资源使用 Thread-3
资源释放 Thread-0
资源使用 Thread-2
资源使用 Thread-4
资源释放 Thread-1
资源释放 Thread-3
资源释放 Thread-4
资源释放 Thread-2


5. 总结


Semaphore是基于AQS和CAS操作实现的共享锁,利用AQS中被volatile修饰的变量state来代表许可证数(permit),当用户调用acquire()方法来获取许可证数时,他会计算出本次取出操作后的许可证数,如果小于0,那么就会挂起等待,如果大于0,也就是许可证数还够用,那么就会使用CAS操作修改许可证数。当调用release()方法释放许可证数时,会唤醒等待的线程去获取共享锁


如果本文对你有帮助,点个小赞吧,后续有时间就多出出这方面的源码解读

相关文章
|
数据采集 分布式计算 DataWorks
maxcompute开发环境问题之申请生产环境和开发环境的表权限失败如何解决
MaxCompute开发环境是指设置和配置用于开发MaxCompute项目的本地或云端环境;本合集将指导用户如何搭建和管理MaxCompute开发环境,包括工具安装、配置和调试技巧。
|
7月前
|
人工智能 自然语言处理 语音技术
CLEF 2026赛道简介:PAN、FinMMEval、CheckThat!(下)
CLEF 2026竞赛包含16个赛道,本文的下半部分简要介绍其中的FinMMEval和CheckThat!赛道
485 5
|
3月前
|
数据安全/隐私保护 开发者 Python
Python的浅拷贝把我坑惨了,原来copy()和deepcopy()的区别这么大
本文以一个因浅拷贝导致权限配置互相污染的Bug为引,深入解析Python中赋值、浅拷贝与深拷贝的本质区别:赋值是引用共享,浅拷贝仅复制顶层容器(内层对象仍共享),深拷贝则递归创建完全独立副本。结合代码实例与形象比喻,厘清适用场景及性能陷阱,助开发者避坑提效。(239字)
246 0
|
存储 容灾 网络性能优化
关于Web服务和内容管理的体验
Web服务和内容管理在当今数字化时代扮演着重要的角色,随着互联网的普及和信息量的爆炸增长,效率和可靠性成为了内容管理系统和Web服务应用程序的关键要素。本文通过阿里云的文件存储NAS的使用体验,探讨其在Web服务和内容管理方面的使用体验。
290 1
关于Web服务和内容管理的体验
|
供应链 物联网 区块链
新技术趋势与应用:探讨新兴技术如区块链、物联网、虚拟现实等的发展趋势和应用场景
【10月更文挑战第29天】随着科技的飞速发展,新兴技术如区块链、物联网、虚拟现实等正逐渐改变我们的生活。本文将对这些技术的发展趋势和应用场景进行深入探讨,以期为读者提供一个全新的视角来理解这些技术的价值和应用前景。
327 0
|
弹性计算 Serverless 调度
《阿里云产品四月刊》—享道出行:容器弹性技术驱动下的智慧出行稳定性实践(3)
阿里云瑶池数据库云原生化和一体化产品能力升级,多款产品更新迭代
430 1
|
人工智能 监控 算法
阿里云PAI-EAS算法服务挑战赛:开启AI新时代
阿里云PAI-EAS算法服务挑战赛:开启AI新时代
723 1
视觉智能平台中,如果你想批量清空人脸样本库里的样本数据
视觉智能平台中,如果你想批量清空人脸样本库里的样本数据
494 5
|
C++ 容器
[C++] 对二维数组中的二维坐标点x,y进行排序
[C++] 对二维数组中的二维坐标点x,y进行排序
604 0
「镁客早报」Facebook再曝数据漏洞;分析师预测谷歌亚马逊明年将推出AirPods竞品
因为一个漏洞,Facebook上有680万用户的私人照片被暴露给不应该看到它们的应用程序。
592 0