我靠!Semaphore里面居然有这么一个大坑! (2)

简介: 我靠!Semaphore里面居然有这么一个大坑! (2)

为什么停不进去呢?他怀疑是死锁了,这个怀疑有点无厘头啊。


我们先回忆一下死锁的四个必要条件:


  • 互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。(不满足,还有两个停车位没有用呢。)


  • 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。(不满足,张三占了一个停车位了,没有提出还要一个停车位的要求,另外的停车位也没有被占用)


  • 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放。(满足,张三的车不开出来,这个停车位理论上是不会被夺走的)


  • 循环等待条件: 若干进程间形成首尾相接循环等待资源的关系。(不满足,只有我和刘能、谢广坤两拨人在等资源,但没有循环等待的情况。)


这四个条件是死锁的必要条件,必要条件就是说只要有死锁了,这些条件必然全部成立。


而经过分析,我们发现没有满足死锁的必要条件。那为什么会出现这样的现象呢?


我们先根据上面的场景,自己写一段代码。



自己撸代码


下面的程序基本上是按照上面截图中的示例代码接合上面的故事改的,可以直接复制粘贴:


public class ParkDemo {
    public static void main(String[] args) throws InterruptedException {
        Integer parkSpace = 3;
        System.out.println("这里有" + parkSpace + "个停车位,先到先得啊!");
        Semaphore semaphore = new Semaphore(parkSpace, true);
        Thread threadA = new Thread(new ParkCar(1, "布加迪", semaphore), "赵四");
        Thread threadB = new Thread(new ParkCar(2, "法拉利", semaphore), "刘能、谢广坤");
        Thread threadC = new Thread(new ParkCar(1, "劳斯莱斯", semaphore), "why哥");
        threadA.start();
        threadB.start();
        threadC.start();
    }
}
class ParkCar implements Runnable {
    private int n;
    private String carName;
    private Semaphore semaphore;
    public ParkCar(int n, String carName, Semaphore semaphore) {
        this.n = n;
        this.carName = carName;
        this.semaphore = semaphore;
    }
    @Override
    public void run() {
        try {
            if (semaphore.availablePermits() < n) {
                System.out.println(Thread.currentThread().getName() + "来停车,但是停车位不够了,等着吧");
            }
            semaphore.acquire(n);
            System.out.println(Thread.currentThread().getName() + "把自己的" + carName + "停进来了,剩余停车位:" + semaphore.availablePermits() + "辆");
            //模拟停车时长
            int parkTime = ThreadLocalRandom.current().nextInt(1, 6);
            TimeUnit.SECONDS.sleep(parkTime);
            System.out.println(Thread.currentThread().getName() + "把自己的" + carName + "开走了,停了" + parkTime + "小时");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            semaphore.release(n);
            System.out.println(Thread.currentThread().getName() + "走后,剩余停车位:" + semaphore.availablePermits() + "辆");
        }
    }
}



image.png


这次这个运行结果和我们预期的是一致的。并没有线程阻塞的现象。


那为什么之前的代码就会出现“在运行时,有时只会执行完线程A,其线程B和线程C都静默了”这种现象呢?


是道德的沦丧,还是人性的扭曲?我带大家走进代码:


运行后的结果如下(由于是多线程环境,运行结果可能不尽相同):


image.png


差异就体现在获取剩余通行证的方法上。上面是链接里面的代码,下面是我自己写的代码。


说实在的,链接里面的代码我最开始硬是眼神编译了一分钟,没有看出问题来。


当我真正把代码粘到 IDEA 里面,跑起来后发现当最先执行了 B 线程后,A、C 线程都可以执行。当最先执行 A 线程的时候,B、C 线程就不会执行。


我人都懵逼了,反复分析,发现这和我认知不一样啊!于是我陷入了沉思:


image.png


过了一会,保洁大爷过来收垃圾,问我:“hi,小帅哥,你这瓶红牛喝完了吧?我把瓶子收走了啊。”然后瞟了一眼屏幕,指着获取剩余许可证的那行代码对我说:“你这个地方方法调用错了哈,你再好好看看方法说明。”


System.out.println("剩余可用许可证: " + semaphore.drainPermits());


说完之后,拍了拍我的肩膀,转身离去。得到大师点化,我才恍然大悟。



image.png


由于获取剩余可用许可证的方法是 drainPermits,所以线程 A 调用完成之后,剩下的许可证为0,然后执行 release 之后,许可证变为 1。(后面会有对应的方法解释)


这时又是一个公平锁,所以,如果线程 B 先进去排队了,剩下的许可证不足以让 B 线程运行,它就一直等着。 C 线程也就没有机会执行。


把获取剩余可用许可证的方法换为 availablePermits 方法后,正常输出:


image.png


方法解释


我估计很多不太了解 semaphore 的朋友看完前面这两部分也还是略微有点懵逼。


没事,所有的疑惑将在这一小节解开。


在上面的测试案例中,我们只用到了 semaphore 的四个方法:


  • availablePermits:获取剩余可用许可证。


  • drainPermits :获取剩余可用许可证。


  • release(int n):释放指定数量的许可证。


  • acquire(int n):申请指定数量的许可证。


首先看 availablePermits 和 drainPermits 这个两个方法的差异:



image.png

目录
相关文章
|
机器学习/深度学习 数据采集 数据挖掘
Python 数据分析入门教程:Numpy、Pandas、Matplotlib和Scikit-Learn详解
Python 数据分析入门教程:Numpy、Pandas、Matplotlib和Scikit-Learn详解
634 0
|
数据库 数据安全/隐私保护 数据库管理
|
5月前
|
负载均衡 安全 Cloud Native
Service Mesh:原则、挑战和演变
服务网格作为云原生架构中的关键组件,旨在解决微服务间通信的复杂性。它通过提供服务发现、负载均衡、安全控制和可观测性等功能,帮助开发者更高效地管理分布式系统。本文探讨了服务网格的起源、核心功能、在多云环境中的应用及其未来发展趋势,展示了其在现代软件架构中的重要价值。
244 10
Service Mesh:原则、挑战和演变
|
JSON 前端开发 安全
layui框架实战案例(22):多附件上传实战开发实录(php后端、文件删除、数据库删除)
layui框架实战案例(22):多附件上传实战开发实录(php后端、文件删除、数据库删除)
1254 0
|
自然语言处理 大数据 应用服务中间件
大数据-172 Elasticsearch 索引操作 与 IK 分词器 自定义停用词 Nginx 服务
大数据-172 Elasticsearch 索引操作 与 IK 分词器 自定义停用词 Nginx 服务
327 5
|
机器学习/深度学习 存储 人工智能
压缩大型语言模型(LLMs):缩小10倍、性能保持不变
尽管大规模语言模型(LLMs)在多种应用场景中表现出色,但其庞大的规模也带来了实际部署难题。本文探讨了通过模型压缩技术解决这些问题的方法,介绍了量化、剪枝和知识蒸馏三种主要压缩技术,并通过具体Python代码示例展示了如何将一个100M参数的文本分类模型压缩至52.8M参数,再通过4位量化进一步减小至原来的1/7,同时保持甚至提升性能。示例代码展示了从数据预处理、模型训练到评估的完整流程,证明了压缩技术的有效性。
888 6
|
存储 Python 容器
python中的h5py开源库的使用
python中的h5py开源库的使用
346 1
|
存储 开发者
CodeWave智能开发平台--03--目标:应用创建--07供应商数据表格01
CodeWave智能开发平台--03--目标:应用创建--07供应商数据表格01
|
Ubuntu Linux 数据安全/隐私保护
Linux的安装过程
Linux的安装过程
398 4
|
运维 监控 Linux
解决CPU与带宽高使用率问题:深入分析与应对策略
引言:性能问题的诊断与优化 在运维工作中,操作系统性能问题如影随形,典型代表是CPU使用率高和带宽使用率高的问题,它们直接影响应用的性能和响应时间。这篇记录将逐个分析这两个问题的产生原因和解决方法。
解决CPU与带宽高使用率问题:深入分析与应对策略