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

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

这两个地方的文档描述,有点玩文字游戏的意思了。稍不留神就被带进去了。


你仔细看:availablePermits 只是 return 当前可用的许可证数量。而 drainPermits 是 acquires and return,它先全部获取后再返回。


availablePermits 只是看看还有多少许可证,drainPermits 是拿走所有剩下的许可证。


所以在上面的场景下,这两个方法的返回值是一样的,但是内部处理完全内部不一样:



image.png


image.png


该方法就是释放指定数量许可证。释放,就意味着许可证的增加。就类似于刘能、谢广坤把他们各自的法拉利从停车位开出来,驶离停车场,这时停车场就会多两个停车位。


上面红框框起来的部分是它的主要逻辑。大家自己看一下,我就不翻译了,大概意思就是释放许可证之后,其他等着用许可证的线程就可以看一下释放之后的许可证数量是否够用,如果够就可以获取许可证,然后运行了。


该方法的精华在 599 到 602 行的说明中:


image.png


这句话非常关键:说的是执行 release 操作的线程不一定非得是执行了 acquire 方法的线程


开发人员,需要根据实际场景来保证 semaphore 的正确使用。


release 操作这里,大家都知道需要放到 finally 代码块里面去执行。但是正是这个认知,是最容易踩坑的地方,而且出了问题还非常不好排查的那种。


放肯定是要放在 finally 代码块里面的,只是怎么放,这里有点讲究。


我接合下一节的例子和 acquire 方法一起说明:


image.png


acquire 方法主要先关注我红框框起来的部分。


从该方法的源码可以看出,会抛出 InterruptException 异常。记住这点,我们在下一节,带入场景讨论。


release使用不当的大坑



我们还是带入之前停车的场景。假设赵四和我先把车停进去了,这个时候刘能、谢广坤他们来了,发现车位不够了,两个好基友嘛,就等着,非要停在一起


image.png


等了一会,我们一直没出来,门口看车的大爷出来对他们说:“我估摸着你们还得等很长时间,别等了,快走吧。”


于是,他们开车离去。


来,就这个场景,整一段代码:


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();
        threadC.start();
        threadB.start();
        //模拟大爷劝退
        threadB.interrupt();
    }
}
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 (InterruptedException e) {
            System.err.println(Thread.currentThread().getName() + "被门口大爷劝走了。");
        } finally {
            semaphore.release(n);
            System.out.println(Thread.currentThread().getName() + "走后,剩余停车位:" + semaphore.availablePermits() + "辆");
        }
    }
}



看着代码是没有毛病,但是运行起来你会发现,有可能出现这样的情况:



image.png


目录
相关文章
|
前端开发 Java 数据安全/隐私保护
深入理解 Spring MVC Controller —— 请求参数获取
前言 接上篇《深入理解 Spring MVC Controller —— 请求映射》,上篇主要介绍了处理器方法及请求映射的定义。有了处理器方法 Spring MVC 就可以对请求进行处理,有了请求映射 Spring MVC 就能知道哪些请求应该由哪些处理器方法来处理。
1078 0
深入理解 Spring MVC Controller —— 请求参数获取
|
JavaScript
VUE element-ui之el-form表单点击按钮自动增加表单(输入框),可新增删除
VUE element-ui之el-form表单点击按钮自动增加表单(输入框),可新增删除
2443 0
VUE element-ui之el-form表单点击按钮自动增加表单(输入框),可新增删除
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
Java多线程同步大揭秘:synchronized与Lock的终极对决!
208 5
|
11月前
|
算法
前向-后向算法
前向-后向算法是隐马尔可夫模型(HMM)中的关键算法,用于计算观测序列的概率及评估模型参数。前向算法通过动态规划计算观测序列出现的概率,而后向算法则计算状态的后验概率。两者结合,广泛应用于概率计算、参数评估和平滑处理,是处理时序数据和序列标注的重要工具。
|
Kubernetes Cloud Native Java
从一个服务预热不生效问题谈微服务无损上线
本文基于阿里云技术服务团队和产研团队,在解决易易互联使用 MSE(微服务引擎)产品无损上线功能所遇到问题的过程总结而成。本文将从问题和解决方法谈起,再介绍相关原理,后进一步拓展到对微服务引擎和云原生网关无损上线能力的介绍。
12048 115
|
监控 安全 IDE
别再瞎用了!synchronized的正确使用姿势在这里!
别再瞎用了!synchronized的正确使用姿势在这里!
571 4
|
弹性计算 监控 JavaScript
【颠覆传统!】云效Flow——让你的CI/CD流程如虎添翼,轻松驾驭高效稳定的自动化部署之旅!
【8月更文挑战第8天】现代软件开发中,持续集成(CI)与持续部署(CD)至关重要。我最近使用了“云效Flow”,一款专为高效稳定的CI/CD流程设计的工具。它支持多种语言与框架,并易于集成第三方服务。只需注册并创建项目,平台便提供新手引导。以Node.js项目为例,代码托管在GitHub上后,在云效Flow中设置流水线,通过YAML自定义构建与测试步骤。代码提交后,构建自动执行。部署环节可利用内置策略,如一键发布到阿里云ECS,并支持蓝绿部署确保平滑切换。此外,云效Flow还具备监控与告警功能。总之,云效Flow简化了CI/CD流程,提高了开发效率与软件质量,适合各种规模的团队使用。
332 2
|
算法 Java 调度
Semaphore实现原理全面解析
Semaphore(信号量)是一个同步工具类,通过Semaphore可以控制同时访问共享资源的线程个数。
|
前端开发 容器