为什么停不进去呢?他怀疑是死锁了,这个怀疑有点无厘头啊。
我们先回忆一下死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。(不满足,还有两个停车位没有用呢。)
- 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。(不满足,张三占了一个停车位了,没有提出还要一个停车位的要求,另外的停车位也没有被占用)
- 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放。(满足,张三的车不开出来,这个停车位理论上是不会被夺走的)
- 循环等待条件: 若干进程间形成首尾相接循环等待资源的关系。(不满足,只有我和刘能、谢广坤两拨人在等资源,但没有循环等待的情况。)
这四个条件是死锁的必要条件,必要条件就是说只要有死锁了,这些条件必然全部成立。
而经过分析,我们发现没有满足死锁的必要条件。那为什么会出现这样的现象呢?
我们先根据上面的场景,自己写一段代码。
自己撸代码
下面的程序基本上是按照上面截图中的示例代码接合上面的故事改的,可以直接复制粘贴:
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() + "辆"); } } }
这次这个运行结果和我们预期的是一致的。并没有线程阻塞的现象。
那为什么之前的代码就会出现“在运行时,有时只会执行完线程A,其线程B和线程C都静默了”这种现象呢?
是道德的沦丧,还是人性的扭曲?我带大家走进代码:
运行后的结果如下(由于是多线程环境,运行结果可能不尽相同):
差异就体现在获取剩余通行证的方法上。上面是链接里面的代码,下面是我自己写的代码。
说实在的,链接里面的代码我最开始硬是眼神编译了一分钟,没有看出问题来。
当我真正把代码粘到 IDEA 里面,跑起来后发现当最先执行了 B 线程后,A、C 线程都可以执行。当最先执行 A 线程的时候,B、C 线程就不会执行。
我人都懵逼了,反复分析,发现这和我认知不一样啊!于是我陷入了沉思:
过了一会,保洁大爷过来收垃圾,问我:“hi,小帅哥,你这瓶红牛喝完了吧?我把瓶子收走了啊。”然后瞟了一眼屏幕,指着获取剩余许可证的那行代码对我说:“你这个地方方法调用错了哈,你再好好看看方法说明。”
System.out.println("剩余可用许可证: " + semaphore.drainPermits());
说完之后,拍了拍我的肩膀,转身离去。得到大师点化,我才恍然大悟。
由于获取剩余可用许可证的方法是 drainPermits,所以线程 A 调用完成之后,剩下的许可证为0,然后执行 release 之后,许可证变为 1。(后面会有对应的方法解释)
这时又是一个公平锁,所以,如果线程 B 先进去排队了,剩下的许可证不足以让 B 线程运行,它就一直等着。 C 线程也就没有机会执行。
把获取剩余可用许可证的方法换为 availablePermits 方法后,正常输出:
方法解释
我估计很多不太了解 semaphore 的朋友看完前面这两部分也还是略微有点懵逼。
没事,所有的疑惑将在这一小节解开。
在上面的测试案例中,我们只用到了 semaphore 的四个方法:
- availablePermits:获取剩余可用许可证。
- drainPermits :获取剩余可用许可证。
- release(int n):释放指定数量的许可证。
- acquire(int n):申请指定数量的许可证。
首先看 availablePermits 和 drainPermits 这个两个方法的差异: