执行结果如下:
threadB get 5 semaphore threadA get 4 semaphore threadA release 1 semaphore threadB release 2 semaphore threadC get 4 semaphore threadA release 1 semaphore threadC release 4 semaphore threadB release 3 semaphore threadA release 2 semaphore threadD get 10 semaphore threadD release 10 semaphore可以看到threadA和threadB在获取了9个信号量之后threadC和threadD之后等待信号量足够时才能继续往下执行。而threadA和threadB在信号量足够时是可以同时执行的。
其中有一个问题,当threadD排队在threadC之前时,信号量如果被释放了4个,threadC会先于threadD执行吗?还是需要排队等待呢?这个疑问在详细分析了Semaphore的源码之后再来给大家答案。
2.2 CountDownLatch的使用 //初始化计数器总量为2 public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(2); CountDownLatchTest(countDownLatch); } private static void CountDownLatchTest(final CountDownLatch countDownLatch) throws InterruptedException { //threadA尝试执行,计数器为2被阻塞 Thread threadA = new Thread(new Runnable() { @Override public void run() { try { countDownLatch.await(); System.out.println(Thread.currentThread().getName() + " await"); } catch (InterruptedException e) { e.printStackTrace(); } } }); threadA.setName("threadA"); //threadB尝试执行,计数器为2被阻塞 Thread threadB = new Thread(new Runnable() { @Override public void run() { try { countDownLatch.await(); System.out.println(Thread.currentThread().getName() + " await"); } catch (InterruptedException e) { e.printStackTrace(); } } }); threadB.setName("threadB"); //threadC在1秒后将计数器数量减1 Thread threadC = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); countDownLatch.countDown(); System.out.println(Thread.currentThread().getName() + " countDown"); } catch (InterruptedException e) { e.printStackTrace(); } } }); threadC.setName("threadC"); //threadD在5秒后将计数器数量减1 Thread threadD = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(5000); countDownLatch.countDown(); System.out.println(Thread.currentThread().getName() + " countDown"); } catch (InterruptedException e) { e.printStackTrace(); } } }); threadD.setName("threadD"); threadA.start(); threadB.start(); threadC.start(); threadD.start(); }执行结果如下:
threadC countDown threadD countDown threadA await threadB awaitthreadA和threadB在尝试执行时由于计数器总量为2被阻塞,当threadC和threadD将计数器总量减为0后,threadA和threadB同时开始执行。
总结一下:Semaphore就像旋转寿司店,共有10个座位,当座位有空余时,等待的人就可以坐上去。如果有只有2个空位,来的是一家3口,那就只有等待。如果来的是一对情侣,就可以直接坐上去吃。当然如果同时空出5个空位,那一家3口和一对情侣可以同时上去吃。CountDownLatch就像大型商场里面的临时游乐场,每一场游乐的时间过后等待的人同时进场玩,而一场中间会有不爱玩了的人随时出来,但不能进入,一旦所有进入的人都出来了,新一批人就可以同时进场。
3. 源码分析明白了Semaphore与CountDownLatch是做什么的,怎么使用的。接下来就来看看Semaphore与CountDownLatch底层时怎么实现这些功能的。
3.1 AQS中共享锁的实现上篇文章通过对ReentrantLock的分析,得倒了AQS中实现独占锁的几个关键方法:
//状态量,独占锁在0和1之间切换 private volatile int state; //调用tryAcquire获取锁,获取失败后加入队列中挂起等操作,AQS中实现 public final void acquire(int arg); //独占模式下尝试获取锁,ReentrantLock中实现 protected boolean tryAcquire(int arg); //调用tryRelease释放锁以及恢复线程等操作,AQS中实现 public final boolean release(int arg); //独占模式下尝试释放锁,ReentrantLock中实现 protected boolean tryRelease(int arg);其中具体的获取和释放独占锁的逻辑都放在ReentrantLock中自己实现,AQS中负责管理获取或释放独占锁成功失败后需要具体处理的逻辑。那么共享锁的实现是否也是遵循这个规律呢?由此我们在AQS中发现了以下几个类似的方法:
//调用tryAcquireShared获取锁,获取失败后加入队列中挂起等操作,AQS中实现 public final void acquireShared(int arg); //共享模式下尝试获取锁 protected int tryAcquireShared(int arg); //调用tryReleaseShared释放锁以及恢复线程等操作,AQS中实现 public final boolean releaseShared(int arg); //共享模式下尝试释放锁 protected boolean tryReleaseShared(int arg);共享锁和核心就在上面4个关键方法中,先来看看Semaphore是怎么调用上述方法来实现共享锁的。
3.2 Semaphore源码分析