据说微信搜索《Java鱼仔》会变动强哦!java
本文收录于JavaStarter ,里面有我完整的Java系列文章,学习或面试均可以看看哦git
(一)概述
资源的分配方式有两种,一种是独占,好比以前讲的ReentrantLock,另一种是共享,即咱们今天将要学习的Semaphore、CyclicBarrier以及CountDownLatch。这些都是JUC包中的类。github
(二)Semaphore
Semaphore是信号量的意思,做用是控制访问特定资源的线程数量。 其核心API为:面试
semaphore.acquire(); semaphore.release();
这么说可能比较模糊,下面我举个例子。微信
Semaphore就比如游乐园中的某个游乐设施的管理员,用来控制同时玩这个游乐设施的人数。好比跳楼机只能坐十我的,就设置Semaphore的permits等于10。ide
每当有一我的来时,首先判断permits是否大于0,若是大于0,就把一个许可证给这我的,同时本身的permits数量减一。源码分析
若是permits数量等于0了,其余人再想进来时就只能排队了。学习
当一我的玩好以后,这我的把许可证还给Semaphore,permits加1,正在排队的人再来竞争这一个许可证。ui
下面经过代码来演示这样一个场景this
public class SemaphoreTest { public static void main(String[] args) { //建立permits等于2 Semaphore semaphore=new Semaphore(2); //开五个线程去执行PlayGame for (int i = 0; i < 5; i++) { new Thread(new PlayGame(semaphore)).start(); } } static class PlayGame extends Thread{ Semaphore semaphore; public PlayGame(Semaphore semaphore){ this.semaphore=semaphore; } @Override public void run() { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"得到一个许可证"); Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+"释放一个许可证"); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
在这里设置Semaphore的permit等于2,表示同时只有两个线程能够执行,而后开五个线程,在执行前经过semaphore.acquire();
获取permit,执行后经过semaphore.release();
归还permit。
经过结果能够观察到,每次最多只会有两个线程执行PlayGame 。
(三)Semaphore原理
3.1 默认非公平锁
Semaphore默认建立的是一个非公平锁:
3.2 Semaphore源码分析
Semaphore的实现方式和ReentrantLock十分相似。
首先定义一个内部类Sync继承AbstractQueuedSynchronizer
从Sync的构造方法中能够看到,初始化时设置state等于permits,在讲ReentrantLock的时候,state用来存储重入锁的次数,在Semaphore中state用来存储资源的数量。
Semaphore的核心方法是acquire和release,当执行acquire方法时,sync会执行一个获取一个共享资源的操做:
核心是判断剩余数量是否大于0,若是是的话就经过cas操做去获取资源,不然就进入队列中等待
当执行release方法时,sync会执行一个将一个共享资源放回去的cas操做
(四)CountDownLatch
countdownlatch可以让一个线程等待其余线程工做完成以后再执行。
countdownlatch经过一个计数器来实现,初始值是指定的数量,每当一个线程完成本身的任务后,计数器减一,当计数器为0时,执行最后的等待线程。
其核心API为
CountDownLatch.countDown(); CountDownLatch.await();
下面来看代码示例:
设定countDownLatch初始值为2,定义两个线程分别执行对应的方法,方法执行完毕后再执行countDownLatch.countDown();
这两个方法执行的过程当中,主线程被countDownLatch.await();
阻塞,只有等到其余线程都执行完毕以后才可执行。
public class CountDownLatchTest { public static void main(String[] args) throws InterruptedException { //设定初始值为2 CountDownLatch countDownLatch=new CountDownLatch(2); //执行两个任务 new Thread(new Task1(countDownLatch)).start(); new Thread(new Task2(countDownLatch)).start(); //在两个任务执行完以后才会执行await方法以后的代码 countDownLatch.await(); System.out.println("其他两个线程执行完以后执行"); } private static class Task1 implements Runnable { private CountDownLatch countDownLatch; public Task1(CountDownLatch countDownLatch) { this.countDownLatch=countDownLatch; } @Override public void run() { System.out.println("执行任务一"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }finally { if (countDownLatch!=null){ //执行完毕后调用countDown countDownLatch.countDown(); } } } } private static class Task2 implements Runnable { private CountDownLatch countDownLatch; public Task2(CountDownLatch countDownLatch) { this.countDownLatch=countDownLatch; } @Override public void run() { System.out.println("执行任务二"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }finally { if (countDownLatch!=null){ //执行完毕后调用countDown countDownLatch.countDown(); } } } } }
效果以下:
(五)CyclicBarrier
栅栏屏障,让一组线程到达一个屏障(也能够叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,全部被屏障拦截的线程才会继续运行。 其核心API为:
cyclicBarrier.await();
和countdownlatch的区别在于,countdownlatch是一个线程等待其余线程执行完毕后再执行,CyclicBarrier是每个线程等待全部线程执行完毕后,再执行。
看代码,初始化cyclicBarrier为3,两个子线程和一个主线程执行完时都会被阻塞在cyclicBarrier.await();
代码前,等三个线程都执行完毕后再执行接下去的代码。
public class CyclicBarrierTest { public static void main(String[] args) throws BrokenBarrierException, InterruptedException { CyclicBarrier cyclicBarrier=new CyclicBarrier(3); System.out.println("执行主线程"); new Thread(new Task1(cyclicBarrier)).start(); new Thread(new Task2(cyclicBarrier)).start(); cyclicBarrier.await(); System.out.println("三个线程都执行完毕,继续执行主线程"); } private static class Task1 implements Runnable { private CyclicBarrier cyclicBarrier; public Task1(CyclicBarrier cyclicBarrier) { this.cyclicBarrier=cyclicBarrier; } @Override public void run() { System.out.println("执行任务一"); try { Thread.sleep(2000); cyclicBarrier.await(); System.out.println("三个线程都执行完毕,继续执行任务一"); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } } private static class Task2 implements Runnable { private CyclicBarrier cyclicBarrier; public Task2(CyclicBarrier cyclicBarrier) { this.cyclicBarrier=cyclicBarrier; } @Override public void run() { System.out.println("执行任务二"); try { Thread.sleep(2000); cyclicBarrier.await(); System.out.println("三个线程都执行完毕,继续执行任务二"); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } } }
结果以下:
cyclicBarrier还能够重复执行,而不须要从新去定义。
public static void main(String[] args) throws BrokenBarrierException, InterruptedException { CyclicBarrier cyclicBarrier=new CyclicBarrier(3); //第一次 System.out.println("执行主线程"); new Thread(new Task1(cyclicBarrier)).start(); new Thread(new Task2(cyclicBarrier)).start(); cyclicBarrier.await(); System.out.println("三个线程都执行完毕,继续执行主线程"); //第二次 System.out.println("执行主线程"); new Thread(new Task1(cyclicBarrier)).start(); new Thread(new Task2(cyclicBarrier)).start(); cyclicBarrier.await(); }
(六)总结
归根结底,Semaphore、CyclicBarrier、CountDownLatch三个类都是对AQS中资源共享的应用,学懂AQS以后,你会发现JUC包中的类变得不难了。好了,咱们下期再见!