我靠!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


目录
相关文章
|
8月前
|
存储 Java
StampedLock(戳记锁)源码解读与使用
StampedLock(戳记锁)源码解读与使用
|
8月前
|
Java 数据库
Semaphore(信号量)源码解读与使用
Semaphore(信号量)源码解读与使用
|
存储 Java
第二季:5公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁【Java面试题】
第二季:5公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁【Java面试题】
67 0
|
8月前
|
Java
Java多线程同步锁、Lock锁和等待唤醒机制及代码演示
Java多线程同步锁、Lock锁和等待唤醒机制及代码演示
181 0
|
设计模式 安全 Java
JUC第十二讲:JUC锁 - 看不懂锁核心类 AQS 原理来打我
JUC第十二讲:JUC锁 - 看不懂锁核心类 AQS 原理来打我
|
Java C++
详解JDK锁01:结合源码一文弄懂Lock接口!
详解JDK锁01:结合源码一文弄懂Lock接口!
137 0
JUC学习(二):Lock的介绍及使用(实现多线程卖票)
JUC学习(二):Lock的介绍及使用(实现多线程卖票)
124 0
|
Java
我靠!Semaphore里面居然有这么一个大坑! (4)
我靠!Semaphore里面居然有这么一个大坑! (4)
113 0
我靠!Semaphore里面居然有这么一个大坑! (4)