一 CountDownLatch

倒数器:当指定值减到0,指定线程开始执行。
CountDownLatch类可以设置一个计数器,让一些线程阻塞直到另外一些线程完成后才被唤醒。
CountDownLatch主要有两个方法,当一个或多个线程调用CountDownLatch.await()方法时,调用线程会被阻塞。其他线程调用countDown方法计数器减一(调用CountDownLatch.countDown()方法时线程不会阻塞),当计数器的值变为0,调用await方法被阻塞的线程才会被唤醒,继续执行。

1.1 案例1:教室关灯

  1. public class CountDownLatchDemo1 {
  2. public static void main(String[] args) throws Exception {
  3. closeDoor();
  4. }
  5. /**
  6. * 关门案例
  7. */
  8. private static void closeDoor() throws InterruptedException {
  9. CountDownLatch countDownLatch = new CountDownLatch(6); // 定义了6次,需要删减6次
  10. for (int i = 1; i <= 6; i++) {
  11. new Thread(() -> {
  12. System.out.println("学生" + Thread.currentThread().getName() + "\t" + "上完自习");
  13. countDownLatch.countDown(); //减一
  14. }, String.valueOf(i)).start();
  15. }
  16. countDownLatch.await(); // 线程阻塞,直到计数减为0
  17. System.out.println(Thread.currentThread().getName() + "\t班长锁门离开教室");
  18. }
  19. }

1.2 案例2:秦灭六国

  1. public class CountDownLatchDemo2 {
  2. public static void main(String[] args) throws Exception {
  3. sixCountry();
  4. }
  5. /**
  6. * 秦灭六国 一统华夏
  7. */
  8. private static void sixCountry() throws InterruptedException {
  9. CountDownLatch countDownLatch = new CountDownLatch(6);
  10. for (int i = 1; i <= 6; i++) {
  11. new Thread(() -> {
  12. System.out.println(Thread.currentThread().getName() + "国,灭亡");
  13. countDownLatch.countDown();
  14. }, CountryEnum.forEach(i).getName()).start();
  15. }
  16. countDownLatch.await();
  17. System.out.println("秦统一");
  18. }
  19. }
  20. enum CountryEnum {
  21. ONE(1, "齐"),
  22. TWO(2, "楚"),
  23. THREE(3, "燕"),
  24. FOUR(4, "赵"),
  25. FIVE(5, "魏"),
  26. SIX(6, "韩");
  27. CountryEnum(Integer code, String name) {
  28. this.code = code;
  29. this.name = name;
  30. }
  31. @Getter
  32. private Integer code;
  33. @Getter
  34. private String name;
  35. public static CountryEnum forEach(int index) {
  36. CountryEnum[] countryEnums = CountryEnum.values();
  37. for (CountryEnum countryEnum : countryEnums) {
  38. if (index == countryEnum.getCode()) {
  39. return countryEnum;
  40. }
  41. }
  42. return null;
  43. }
  44. }

1.3 案例3:倒计时

  1. public static void test1() {
  2. CountDownLatch countDownLatch = new CountDownLatch(3);
  3. new Thread(() -> {
  4. try {
  5. TimeUnit.SECONDS.sleep(1);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. System.out.println("数据1初始化");
  10. countDownLatch.countDown();
  11. try {
  12. TimeUnit.SECONDS.sleep(2);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println("数据2初始化");
  17. countDownLatch.countDown();
  18. try {
  19. TimeUnit.SECONDS.sleep(1);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. System.out.println("数据3初始化");
  24. countDownLatch.countDown();
  25. }).start();
  26. try {
  27. countDownLatch.await();
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. System.out.println("数据全部完成");
  32. }

二 CyclicBarrier

计数器:当线程运行数达到一定数量,就开放屏障
CyclicBarrier的字面意思是可循环(Cyclic) 使用的屏障(barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫做同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法。
当拥有指定数量的线程执行了 cyclicBarrier.await 方法时,cyclicBarrier中的线程和其他线程才会继续执行。

2.1 案例:召唤神龙

当只有凑齐 七 颗龙珠,才能最后成功召唤神龙

  1. public class CyclicBarrierDemo {
  2. public static void main(String[] args) {
  3. CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
  4. // 当所有的线程达到屏障,才会执行当前方法
  5. System.out.println("召唤神龙");
  6. });
  7. for (int i = 1; i <= 7; i++) {
  8. final int temp = i;
  9. new Thread(() -> {
  10. System.out.println(Thread.currentThread().getName() + "\t 收集到第" + temp + "颗龙珠");
  11. try {
  12. cyclicBarrier.await();
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. } catch (BrokenBarrierException e) {
  16. e.printStackTrace();
  17. }
  18. }, String.valueOf(i)).start();
  19. }
  20. }
  21. }

三 Semaphore

信号量(Semaphore)和CountDownLatch一样,内部维护一个核心属性sync,通过AQS的共享锁机制实现。在内部维护着指定数量的许可证,并根据许可证数量进行颁发给尝试获取它的线程
信号量的主要用户两个目的:
1)一个是用于多个共享资源的相互排斥使用
2)另一个用于并发资源数的控制

3.1 核心方法

  1. Semaphore(int premits,boolean fair): 构造器方法。permits为信号量初始化数量,第二个参数fair可以设置是否需要公平策略,如果传入true,那么Semaphore会把等待的线程放入FIFO队列中,以便许可证被释放后,可以分配给等待时间最长的线程
  2. Semaphore(int n):构造器,并初始化设置许可证数量。
  3. acquire(): 试图获取许可证,如果当前没有可用的,就会进入阻塞等待状态
  4. tryAcquire(): 试图获取许可证,如果是否能够获取,都不会进入阻塞
  5. tryAcquire(long timeout, TimeUnit unit): 和tryAcquire一样,只是多了一个超时时间,等待指定时间还获取不到许可证,就会停止等待
  6. availablePermits():返回此信号量当前可用的许可证数量。此方法常用于调试和测试目的。
  7. release(): 释放一个许可证
  8. release(int permits): 释放指定数量的许可证。

    3.2 案例:抢车位

    1. public class SemaphoreDemo {
    2. public static void main(String[] args) {
    3. // 1、模拟3个停车位
    4. Semaphore semaphore = new Semaphore(3);
    5. // 模拟6部汽车
    6. for (int i = 1; i <= 6; i++) {
    7. new Thread(() -> {
    8. try {
    9. // 2、抢到资源
    10. semaphore.acquire();
    11. System.out.println(Thread.currentThread().getName() + "\t抢到车位");
    12. try {
    13. TimeUnit.SECONDS.sleep(3);
    14. } catch (InterruptedException e) {
    15. e.printStackTrace();
    16. }
    17. System.out.println(Thread.currentThread().getName() + "\t 停3秒离开车位");
    18. } catch (InterruptedException e) {
    19. e.printStackTrace();
    20. } finally {
    21. // 3、释放资源
    22. semaphore.release();
    23. }
    24. }, String.valueOf(i)).start();
    25. }
    26. }
    27. }