Semaphore

news2024/11/16 18:06:09

文章目录

  • 基本使用
  • Semaphore 应用-改进数据库连接池
  • Semaphore 原理
    • 1. 加锁解锁流程
    • 2. 源码分析

基本使用

信号量,用来限制能同时访问共享资源的线程上限。

 public static void main(String[] args) {
        // 1. 创建 semaphore 对象
        Semaphore semaphore = new Semaphore(3);
        // 2. 10个线程同时运行
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                // 3. 获取许可
                try {
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    log.debug("running...");
                    sleep(1);
                    log.debug("end...");
                } finally {
                    // 4. 释放许可
                    semaphore.release();
                }
            }).start();
        }
    }

输出

07:35:15.485 c.TestSemaphore [Thread-2] - running... 
07:35:15.485 c.TestSemaphore [Thread-1] - running... 
07:35:15.485 c.TestSemaphore [Thread-0] - running... 
07:35:16.490 c.TestSemaphore [Thread-2] - end... 
07:35:16.490 c.TestSemaphore [Thread-0] - end... 
07:35:16.490 c.TestSemaphore [Thread-1] - end... 
07:35:16.490 c.TestSemaphore [Thread-3] - running... 
07:35:16.490 c.TestSemaphore [Thread-5] - running... 
07:35:16.490 c.TestSemaphore [Thread-4] - running... 
07:35:17.490 c.TestSemaphore [Thread-5] - end... 
07:35:17.490 c.TestSemaphore [Thread-4] - end... 
07:35:17.490 c.TestSemaphore [Thread-3] - end... 
07:35:17.490 c.TestSemaphore [Thread-6] - running... 
07:35:17.490 c.TestSemaphore [Thread-7] - running... 
07:35:17.490 c.TestSemaphore [Thread-9] - running... 
07:35:18.491 c.TestSemaphore [Thread-6] - end... 
07:35:18.491 c.TestSemaphore [Thread-7] - end... 
07:35:18.491 c.TestSemaphore [Thread-9] - end... 
07:35:18.491 c.TestSemaphore [Thread-8] - running... 
07:35:19.492 c.TestSemaphore [Thread-8] - end... 

Semaphore 应用-改进数据库连接池

semaphore 实现

  • 使用 Semaphore 限流,在访问高峰期时,让请求线程阻塞,高峰期过去再释放许可,当然它只适合限制单机线程数量,并且仅是限制线程数,而不是限制资源数(例如连接数,请对比 Tomcat LimitLatch 的实现)
  • 用 Semaphore 实现简单连接池,对比『享元模式』下的实现(用wait notify),性能和可读性显然更好,注意下面的实现中线程数和数据库连接数是相等的
@Slf4j(topic = "c.Pool")
class Pool {
    // 1. 连接池大小
    private final int poolSize;

    // 2. 连接对象数组
    private Connection[] connections;

    // 3. 连接状态数组 0 表示空闲, 1 表示繁忙
    private AtomicIntegerArray states;

    private Semaphore semaphore;

    // 4. 构造方法初始化
    public Pool(int poolSize) {
        this.poolSize = poolSize;
        // 让许可数与资源数一致
        this.semaphore = new Semaphore(poolSize);
        this.connections = new Connection[poolSize];
        this.states = new AtomicIntegerArray(new int[poolSize]);
        for (int i = 0; i < poolSize; i++) {
            connections[i] = new MockConnection("连接" + (i+1));
        }
    }

    // 5. 借连接
    public Connection borrow() {// t1, t2, t3
        // 获取许可
        try {
            semaphore.acquire(); // 没有许可的线程,在此等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < poolSize; i++) {
            // 获取空闲连接
            if(states.get(i) == 0) {
                if (states.compareAndSet(i, 0, 1)) {
                    log.debug("borrow {}", connections[i]);
                    return connections[i];
                }
            }
        }
        // 不会执行到这里
        return null;
    }
    // 6. 归还连接
    public void free(Connection conn) {
        for (int i = 0; i < poolSize; i++) {
            if (connections[i] == conn) {
                states.set(i, 0);
                log.debug("free {}", conn);
                semaphore.release();
                break;
            }
        }
    }
}

Semaphore 原理

1. 加锁解锁流程

Semaphore 有点像一个停车场,permits 就好像停车位数量,当线程获得了 permits 就像是获得了停车位,然后
停车场显示空余车位减一

刚开始,permits(state)为 3,这时 5 个线程来获取资源
在这里插入图片描述

假设其中 Thread-1,Thread-2,Thread-4 cas 竞争成功,而 Thread-0 和 Thread-3 竞争失败,进入 AQS 队列park 阻塞
在这里插入图片描述
这时 Thread-4 释放了 permits,状态如下
在这里插入图片描述
接下来 Thread-0 竞争成功,permits 再次设置为 0,设置自己为 head 节点,断开原来的 head 节点,unpark 接下来的 Thread-3 节点,但由于 permits 是 0,因此 Thread-3 在尝试不成功后再次进入 park 状态
在这里插入图片描述

2. 源码分析

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;
    NonfairSync(int permits) {
        // permits 即 state
        super(permits);
    }

    // Semaphore 方法, 方便阅读, 放在此处
    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    // AQS 继承过来的方法, 方便阅读, 放在此处
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

    // 尝试获得共享锁
    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }

    // Sync 继承过来的方法, 方便阅读, 放在此处
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            int available = getState();
            int remaining = available - acquires;
            if (
                // 如果许可已经用完, 返回负数, 表示获取失败, 进入 doAcquireSharedInterruptibly
                    remaining < 0 ||
                            // 如果 cas 重试成功, 返回正数, 表示获取成功
                            compareAndSetState(available, remaining)
            ) {
                return remaining;
            }
        }
    }

    // AQS 继承过来的方法, 方便阅读, 放在此处
    private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    // 再次尝试获取许可
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        // 成功后本线程出队(AQS), 所在 Node设置为 head
                        // 如果 head.waitStatus == Node.SIGNAL ==> 0 成功, 下一个节点 unpark
                        // 如果 head.waitStatus == 0 ==> Node.PROPAGATE
// r 表示可用资源数, 为 0 则不会继续传播
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                // 不成功, 设置上一个节点 waitStatus = Node.SIGNAL, 下轮进入 park 阻塞
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    // Semaphore 方法, 方便阅读, 放在此处
    public void release() {
        sync.releaseShared(1);
    }

    // AQS 继承过来的方法, 方便阅读, 放在此处
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

// Sync 继承过来的方法, 方便阅读, 放在此处
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;
    }
}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1658455.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

let命令

let 命令 let 与 var 二者区别&#xff1a; 作用域不同&#xff1a;变量提升&#xff08;Hoisting&#xff09;&#xff1a;临时性死区重复声明&#xff1a; 联系&#xff1a;举例说明&#xff1a; 块级作用域 块级作用域的关键字使用 var&#xff08;无块级作用域&#xff09;…

C++中的std::bind深入剖析

目录 1.概要 2.原理 3.源码分析 3.1._Binder分析 3.2._CALL_BINDER的实现 4.总结 1.概要 std::bind是C11 中的一个函数模板&#xff0c;用于创建一个可调用对象&#xff08;函数对象或者函数指针&#xff09;的绑定副本&#xff0c;其中一部分参数被固定为指定值&#xf…

​​​​【收录 Hello 算法】4.4 内存与缓存

目录 4.4 内存与缓存 4.4.1 计算机存储设备 4.4.2 数据结构的内存效率 4.4.3 数据结构的缓存效率 4.4 内存与缓存 在本章的前两节中&#xff0c;我们探讨了数组和链表这两种基础且重要的数据结构&#xff0c;它们分别代表了“连续存储”和“分散存储”两种物理…

Qt常用基础控件总结

一、按钮部件 按钮部件共同特性 Qt 用于描述按钮部件的类、继承关系、各按钮的名称和样式,如下图: 助记符:使用字符"&“可在为按钮指定文本标签时设置快捷键,在&之后的字符将作为快捷键。比如 “A&BC” 则 Alt+B 将成为该按钮的快捷键,使用”&&qu…

铁山靠之数学建模 - Matlab入门

Matlab基础 1. Matlab界面与基本操作1.1 matlab帮助系统1.2 matlab命令1.3 matlab功能符号1.4 matlab的数据类型1.5 函数计算1.6 matlab向量1.7 matlab多项式1.8 M文件1.9 函数文件1.10 matlab的程序结构1.11 echo、warning和error函数1.12 交互输入1.13 程序调试1.14 设置断点…

游戏陪玩平台app小程序H5源码交付游戏陪玩接单软件游戏陪玩源码 陪玩小程序陪玩工作室运营模式陪玩管理系统游戏陪玩工作室怎么做

提供陪玩平台源码&#xff0c;陪玩系统源码&#xff0c;陪玩app源码&#xff0c;团队各部门配备齐全&#xff0c;分工明确&#xff0c;及时对接开发进度&#xff0c;保证开发效率 一、陪玩平台源码的功能介绍 1、派单大厅:陪玩系统源码的派单大厅内支持用户通过语音连麦的方式…

idea已配置的git仓库地址 更换新的Git仓库地址 教程

文章目录 目录 文章目录 更改流程 小结 概要更改流程技术细节小结 概要 先在idea控制台走一下流程 先将本地的git仓库删除 1. 查看当前远程仓库地址&#xff1a; 在终端或命令行中&#xff0c;导航到你的项目目录&#xff0c;并运行以下命令查看当前的远程仓库地址&#xff…

QT+MYSQL数据库处理

1、打印Qt支持的数据库驱动&#xff0c;看是否有MYSQL数据库驱动 qDebug() << QSqlDatabase::drivers(); 有打印结果可知&#xff0c;没有MYSQL数据库的驱动 2、下载MYSQL数据库驱动&#xff0c;查看下面的文章配置&#xff0c;亲测&#xff0c;可以成功 Qt6 配置MySQL…

智能BI(后端)-- 系统异步化

文章目录 系统问题分析什么是异步化&#xff1f;业务流程分析标准异步化的业务流程系统业务流程 线程池为什么需要线程池&#xff1f;线程池两种实现方式线程池的参数线程池的开发 项目异步化改造 系统问题分析 问题场景&#xff1a;调用的服务能力有限&#xff0c;或者接口的…

phpstudy(MySQL启动又立马停止)问题的解决办法

方法一&#xff1a;查看本地安装的MySQL有没有启动 1.鼠标右击开始按钮选择计算机管理 2.点击服务和应用程序 3.找到服务双击 4.找到MySQL服务 5.双击查看是否启动&#xff0c;如启动则停止他&#xff0c;然后确定&#xff0c;重新打开phpstudy,启动Mysql. 方法二&#xff…

OpenHarmony 实战开发——3.1 Release + Linux 原厂内核Launcher起不来问题分析报告

1、关键字 Launcher 无法启动&#xff1b;原厂内核&#xff1b;Access Token ID&#xff1b; 2、问题描述 芯片&#xff1a;rk3566&#xff1b;rk3399 内核版本&#xff1a;Linux 4.19&#xff0c;是 RK 芯片原厂发布的 rk356x 4.19 稳定版内核 OH 版本&#xff1a;OpenHa…

net7部署经历

1、linux安装dotnet命令&#xff1a; sudo yum install dotnet-sdk-7.0 或者直接在商店里安装 2、配置反向代理 127.0.0.1:5000》localhost 访问后报错 原因&#xff1a;数据表驼峰名&#xff0c; 在windows的数据表不区分大小写&#xff0c;但是在linux里面是默认区分的&…

xiuno(修罗)知乎模板二开优化魔板仿网盘资源社–模板加全套插件

使用说明 以服务器为例搭建教程 ①先安装 PHP7.1 版本 再安装数据库 Mysql ②解压文件&#xff1a;xiunobbs_4.0.4&#xff08;解压到根目录&#xff09;.zip ③解压②完成后找到【plugin】文件夹再解压&#xff1a;plugin(解压到 plugin 文件夹).zip 设置伪静态代码在上面&am…

记录如何查询域名txt解析是否生效

要查询域名的TXT记录&#xff0c;可以使用nslookup命令。具体步骤如下&#xff1a;12 打开命令行终端。输入命令 nslookup -qttxt 域名&#xff0c;将"域名"替换为你要查询的实际域名。执行命令后&#xff0c;nslookup会返回域名的TXT记录值。 如何查询域名txt解析是…

【C++后端项目】负载均衡OJ服务器

文章目录 一、演示项目二、所用技术与开发环境所用技术开发环境 三、项目宏观结构I. 风格&#xff1a;仿leetcodeII. 结构&#xff1a;Browser-Server模式III. 编写思路&#xff1a;编译服务 -> OJ服务 -> 前端设计 四、关于Git分支管理✨4.1 Git 分支结构4.2 Git 分支命…

【linux】主分区,扩展分区,逻辑分区,动态分区,引导分区,标准分区

目录 主分区&#xff0c;扩展分区&#xff0c;逻辑分区 主分区和引导分区 主分区&#xff0c;扩展分区&#xff0c;逻辑分区&#xff08;标准分区&#xff09; 硬盘一般划分为一个“主分区”和“扩展分区”&#xff0c;然后在扩展分区上再分成数个逻辑分区。 磁盘主分区扩展…

调用 gradio 创建聊天网页报错(使用远程服务器)

文章目录 写在前面1、使用默认IP地址&#xff08;失败&#xff09;2、使用本地IP地址&#xff08;失败&#xff09;3、使用远程服务器IP地址&#xff08;成功&#xff09; 写在前面 我复现了github上的 llama-chinese 的工作 使用的是 llama2&#xff0c;环境配置是在远程服务…

如何使用 ArcGIS Pro 计算容积率

容积率是指地上建筑物的总面积与用地面积的比率&#xff0c;数值越小越舒适&#xff0c;这里为大家介绍一下如何使用ArcGIS Pro 计算容积率&#xff0c;希望能对你有所帮助。 数据来源 教程所使用的数据是从水经微图中下载的建筑和小区数据&#xff0c;除了建筑和小区数据&am…

智能合约是什么?搭建与解析

智能合约是一种基于区块链技术的自动化执行合约&#xff0c;它通过编程语言编写&#xff0c;并在区块链网络上部署运行。智能合约是区块链技术的重要组成部分&#xff0c;它使得去中心化应用&#xff08;DApp&#xff09;的开发变得更加便捷和高效。本文将从智能合约的搭建、原…

如何解决 NPM依赖下载超时问题 :npm ERR! network timeout at: https://registry.npmjs.org/猫头虎

如何解决 NPM依赖下载超时问题 &#xff1a;npm ERR! network timeout at: https://registry.npmjs.org/猫头虎 博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试…