Chao's Blog Chao's Blog
首页
  • vue

    • vue路由
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • java
  • spring
  • springboot
  • springcloud
  • git
  • maven
  • nginx
  • tomcat
  • springmvc
  • jvm
  • 图数据库
  • mysql数据库
  • redis数据库
  • windows下docker安装nginx并挂载目录
  • linux命令
  • linux安装软件
  • linux脚本
  • idea
  • vscode
  • 归档
  • 综合项目

    • 若依项目
    • mall项目
  • java
  • mybatis
  • xxl-job
  • mybatis
GitHub (opens new window)

~chao

永远菜鸟,不断努力~
首页
  • vue

    • vue路由
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • java
  • spring
  • springboot
  • springcloud
  • git
  • maven
  • nginx
  • tomcat
  • springmvc
  • jvm
  • 图数据库
  • mysql数据库
  • redis数据库
  • windows下docker安装nginx并挂载目录
  • linux命令
  • linux安装软件
  • linux脚本
  • idea
  • vscode
  • 归档
  • 综合项目

    • 若依项目
    • mall项目
  • java
  • mybatis
  • xxl-job
  • mybatis
GitHub (opens new window)
  • java

    • java深度克隆
    • restemplate

    • hashcode()和equals()的作用、区别、联系
    • java8新特性
    • 接口与抽象类
    • java深度克隆工具类支持对象和list克隆
    • 子线程执行10次后,主线程再运行5次,这样交替执行三遍
    • CountDownLatch的理解和使用
    • CAS详解
    • java中的各种锁详细介绍
    • java线程池
    • Java锁之偏向级锁、轻量级锁、重量级锁
    • Transactional注解与try{}catch(Exception e){}同时使用事务控制无效问题
    • JAVA8 optional用法
    • CyclicBarrier 使用详解
    • Semaphore 使用及原理
      • Java中七大垃圾回收器
    • spring

    • springboot

    • springcloud

    • git

    • maven

    • nginx

    • tomcat

    • springmvc

    • jvm

    • 正则表达式

    • 消息中间件

    • python

    • 后端
    • java
    ~chao
    2023-02-20
    目录

    Semaphore 使用及原理

    # Semaphore 使用及原理

    原文链接:Semaphore 使用及原理 - 知乎 (zhihu.com) (opens new window)

    # 1、Semaphore 是什么

    Semaphore 通常我们叫它信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。

    可以把它简单的理解成我们停车场入口立着的那个显示屏,每有一辆车进入停车场显示屏就会显示剩余车位减1,每有一辆车从停车场出去,显示屏上显示的剩余车辆就会加1,当显示屏上的剩余车位为0时,停车场入口的栏杆就不会再打开,车辆就无法进入停车场了,直到有一辆车从停车场出去为止。

    # 2、使用场景

    通常用于那些资源有明确访问数量限制的场景,常用于限流 。

    比如:数据库连接池,同时进行连接的线程有数量限制,连接不能超过一定的数量,当连接达到了限制数量后,后面的线程只能排队等前面的线程释放了数据库连接才能获得数据库连接。

    比如:停车场场景,车位数量有限,同时只能容纳多少台车,车位满了之后只有等里面的车离开停车场外面的车才可以进入。

    # 3、Semaphore常用方法说明

    acquire()  
    获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
    
    acquire(int permits)  
    获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
        
    acquireUninterruptibly() 
    获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。
        
    tryAcquire()
    尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。
    
    tryAcquire(long timeout, TimeUnit unit)
    尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。
    
    release()
    释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
    
    hasQueuedThreads()
    等待队列里是否还存在等待线程。
    
    getQueueLength()
    获取等待队列里阻塞的线程数。
    
    drainPermits()
    清空令牌把可用令牌数置为0,返回清空令牌的数量。
    
    availablePermits()
    返回可用的令牌数量。
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

    # 4、用semaphore 实现停车场提示牌功能。

    每个停车场入口都有一个提示牌,上面显示着停车场的剩余车位还有多少,当剩余车位为0时,不允许车辆进入停车场,直到停车场里面有车离开停车场,这时提示牌上会显示新的剩余车位数。

    业务场景 :

    1、停车场容纳总停车量10。

    2、当一辆车进入停车场后,显示牌的剩余车位数响应的减1.

    3、每有一辆车驶出停车场后,显示牌的剩余车位数响应的加1。

    4、停车场剩余车位不足时,车辆只能在外面等待。

    代码:

    public class TestCar {
    
        //停车场同时容纳的车辆10
        private  static  Semaphore semaphore=new Semaphore(10);
    
        public static void main(String[] args) {
    
            //模拟100辆车进入停车场
            for(int i=0;i<100;i++){
    
                Thread thread=new Thread(new Runnable() {
                    public void run() {
                        try {
                            System.out.println("===="+Thread.currentThread().getName()+"来到停车场");
                            if(semaphore.availablePermits()==0){
                                System.out.println("车位不足,请耐心等待");
                            }
                            semaphore.acquire();//获取令牌尝试进入停车场
                            System.out.println(Thread.currentThread().getName()+"成功进入停车场");
                            Thread.sleep(new Random().nextInt(10000));//模拟车辆在停车场停留的时间
                            System.out.println(Thread.currentThread().getName()+"驶出停车场");
                            semaphore.release();//释放令牌,腾出停车场车位
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                },i+"号车");
    
                thread.start();
    
            }
    
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35

    # 5、Semaphore实现原理

    # (1)、Semaphore初始化。

    Semaphore semaphore=new Semaphore(2);
    
    1

    1、当调用new Semaphore(2) 方法时,默认会创建一个非公平的锁的同步阻塞队列。

    2、把初始令牌数量赋值给同步队列的state状态,state的值就代表当前所剩余的令牌数量。

    初始化完成后同步队列信息如下图:

    # (2)获取令牌

    semaphore.acquire();
    
    1

    1、当前线程会尝试去同步队列获取一个令牌,获取令牌的过程也就是使用原子的操作去修改同步队列的state ,获取一个令牌则修改为state=state-1。

    2、 当计算出来的state<0,则代表令牌数量不足,此时会创建一个Node节点加入阻塞队列,挂起当前线程。

    3、当计算出来的state>=0,则代表获取令牌成功。

    源码:

    /**
         *  获取1个令牌
         */
        public void acquire() throws InterruptedException {
            sync.acquireSharedInterruptibly(1);
        }
    
    1
    2
    3
    4
    5
    6
    /**
         * 共享模式下获取令牌,获取成功则返回,失败则加入阻塞队列,挂起线程
         * @param arg
         * @throws InterruptedException
         */
        public final void acquireSharedInterruptibly(int arg)
                throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //尝试获取令牌,arg为获取令牌个数,当可用令牌数减当前令牌数结果小于0,则创建一个节点加入阻塞队列,挂起当前线程。
            if (tryAcquireShared(arg) < 0)
                doAcquireSharedInterruptibly(arg);
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /**
         * 1、创建节点,加入阻塞队列,
         * 2、重双向链表的head,tail节点关系,清空无效节点
         * 3、挂起当前节点线程
         * @param arg
         * @throws InterruptedException
         */
        private void doAcquireSharedInterruptibly(int arg)
            throws InterruptedException {
            //创建节点加入阻塞队列
            final Node node = addWaiter(Node.SHARED);
            boolean failed = true;
            try {
                for (;;) {
                    //获得当前节点pre节点
                    final Node p = node.predecessor();
                    if (p == head) {
                        int r = tryAcquireShared(arg);//返回锁的state
                        if (r >= 0) {
                            setHeadAndPropagate(node, r);
                            p.next = null; // help GC
                            failed = false;
                            return;
                        }
                    }
                    //重组双向链表,清空无效节点,挂起当前线程
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        throw new InterruptedException();
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35

    线程1、线程2、线程3、分别调用semaphore.acquire(),整个过程队列信息变化如下图:

    # (3)、释放令牌

     semaphore.release();
    
    1

    当调用semaphore.release() 方法时

    1、线程会尝试释放一个令牌,释放令牌的过程也就是把同步队列的state修改为state=state+1的过程

    2、释放令牌成功之后,同时会唤醒同步队列中的一个线程。

    3、被唤醒的节点会重新尝试去修改state=state-1 的操作,如果state>=0则获取令牌成功,否则重新进入阻塞队列,挂起线程。

    源码:

     /**
         * 释放令牌
         */
        public void release() {
            sync.releaseShared(1);
        }
    
    1
    2
    3
    4
    5
    6
    /**
         *释放共享锁,同时会唤醒同步队列中的一个线程。
         * @param arg
         * @return
         */
        public final boolean releaseShared(int arg) {
            //释放共享锁
            if (tryReleaseShared(arg)) {
                //唤醒所有共享节点线程
                doReleaseShared();
                return true;
            }
            return false;
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     /**
         * 唤醒同步队列中的一个线程
         */
        private void doReleaseShared() {
            for (;;) {
                Node h = head;
                if (h != null && h != tail) {
                    int ws = h.waitStatus;
                    if (ws == Node.SIGNAL) {//是否需要唤醒后继节点
                        if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//修改状态为初始0
                            continue;
                        unparkSuccessor(h);//唤醒h.nex节点线程
                    }
                    else if (ws == 0 &&
                             !compareAndSetWaitStatus(h, 0, Node.PROPAGATE));
                }
                if (h == head)                   // loop if head changed
                    break;
            }
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    继上面的图,当我们线程1调用semaphore.release(); 时候整个流程如下图:

    编辑 (opens new window)
    上次更新: 2024/01/26, 05:03:22
    CyclicBarrier 使用详解
    Java中七大垃圾回收器

    ← CyclicBarrier 使用详解 Java中七大垃圾回收器→

    最近更新
    01
    python使用生成器读取大文件-500g
    09-24
    02
    Windows环境下 Docker Desktop 安装 Nginx
    04-10
    03
    使用nginx部署多个前端项目(三种方式)
    04-10
    更多文章>
    Theme by Vdoing | Copyright © 2022-2024 chaos | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式