Semaphore原理剖析

1. 简介

简单描述Semaphore 的功能,那就是

信号量 Semaphore 是一个控制访问多个共享资源的计数器,和 CountDownLatch 一样,其本质上是一个“共享锁”

2. 实现原理

在这里插入图片描述

  • 在Semaphore声明阶段对许可量进行初始化,配置许可量数量permit
  • 调用acquire方法会获取permit,这里默认获取一个,也可以传入获取的许可量数量一次获取多个;当信号量数量为0时调用线程会进入阻塞等待状态;信号量的获取方法提供了公平锁非公平锁获取两种方式
  • 调用release方法会释放permit,将信号量归还以供其他线程获取

3. 源码结构

在这里插入图片描述

  • J.U.C包中的最核心部分就是AQS的实现,它是JDK并发工具的实现基石,Semaphore 是基于AQS进行实现的
  • acquire、release方法分别调用了AQS的tryAcquireShared、tryReleaseShared方法对AQS的共享变量state进行操作;基于自身Sync提供了FairSync公平锁NonFairSync非公平锁的实现
  • acquire方法获取许可量,支持单个或多个获取,即信号量递减;当许可量为0时,进行线程阻塞等待
  • release方法用来控制许可量,对其进行释放,即信号量增加

4. 源码剖析

4.1 acquire方法

在这里插入图片描述

  • 调用acquire() 方法,开始获取信号量许可
  • 该方法内部使用 AQS 的 acquireSharedInterruptibly(int arg) 方法
  • 在内部类提供了FairSync和NonFairSync两种实现重写 tryAcquireShared(int arg) 的方法,即公平锁非公平锁的实现
  • 通过AQS中的getState() 方法,获取同步状态,即信号量许可数
  • 如果计数器值等于 0,则会自旋,尝试一直去获取直到许可数大于0,即有可以允许的信号量为止

4.2 release方法

在这里插入图片描述

  • 调用release() 方法,来释放信号量,或者说是归还信号量,实际是将信号量增加,可以让其他线程有机会获取到共享变量进行执行
  • 内部调用AQS的releaseShared() 方法
  • Sync重写了tryReleaseShared() 方法,这里和CountDownLatch的实现类似
  • 释放锁,也就是操作计数器的过程,这里使用到了CAS(compareAndSetState)进行计数更新,若更新失败则进行自旋重试直到成功为止

5. 实战用例

汽车准乘人数有限,模拟两个旅行团乘客上车

/**
 * created by guanjian on 2020/12/28 15:31
 */
public class SemaphoreTest {

    //模拟汽车准乘人数
    private final static Semaphore semaphore = new Semaphore(5);

    //A旅行团人数5人
    private final static int A_NUMS = 5;
    //B旅行团人数5人
    private final static int B_NUMS = 5;

    public static void main(String[] args) throws InterruptedException {
        //A旅行团上车
        new Thread(() -> {
            IntStream.range(0,A_NUMS).forEach(x->{
                try {
                    System.out.format("当前汽车准乘人数=%s \n", semaphore.availablePermits());
                    semaphore.acquireUninterruptibly();
                    System.out.println("A旅行团上车1人 \n");
                    Thread.sleep(new Random().nextInt(3000));
                    System.out.format("剩余汽车准乘人数=%s \n", semaphore.availablePermits());
                }catch (Exception e){
                    e.printStackTrace();
                }
            });
        }).start();

        //B旅行团上车
        new Thread(() -> {
            IntStream.range(0,B_NUMS).forEach(x->{
                try {
                    System.out.format("当前汽车准乘人数=%s \n", semaphore.availablePermits());
                    semaphore.acquireUninterruptibly();
                    System.out.println("B旅行团上车1人 \n");
                    Thread.sleep(new Random().nextInt(3000));
                    System.out.format("剩余汽车准乘人数=%s \n", semaphore.availablePermits());
                }catch (Exception e){
                    e.printStackTrace();
                }
            });
        }).start();
    }
}

6. 总结

  • 实现本质还是通过操作AQS的state实现多线程的通信交互
  • 相比CountDownLatch的实现,Semaphore可以对state进行数量的增加、减少的单个或多个操作
  • 获取共享变量的实现,即获取锁的实现提供了公平锁、非公平锁的实现,选择性更多
  • 释放锁即变更共享变量递增的实现,通过自旋锁和CAS配合完成,这与其他JUC并发工具如CountDownLatch类似

7. 参考

http://www.iocoder.cn/JUC/sike/Semaphore/

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大摩羯先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值