Code Ease Code Ease
  • 个人博客网站 (opens new window)
  • 好用的工具网站 (opens new window)
  • Java核心基础
  • 框架的艺术
  • 分布式与微服务
  • 开发经验大全
  • 设计模式
  • 版本新特性
数据库系列
大数据+AI
  • xxl-job
运维与Linux
  • 基于SpringBoot和BootStrap的论坛网址
  • 基于VuePress的个人博客网站
  • 基于SpringBoot开发的小功能
  • 做一个自己的IDEA插件
程序人生
关于我
  • 分类
  • 标签
  • 归档

神秘的鱼仔

你会累是因为你在走上坡路
  • 个人博客网站 (opens new window)
  • 好用的工具网站 (opens new window)
  • Java核心基础
  • 框架的艺术
  • 分布式与微服务
  • 开发经验大全
  • 设计模式
  • 版本新特性
数据库系列
大数据+AI
  • xxl-job
运维与Linux
  • 基于SpringBoot和BootStrap的论坛网址
  • 基于VuePress的个人博客网站
  • 基于SpringBoot开发的小功能
  • 做一个自己的IDEA插件
程序人生
关于我
  • 分类
  • 标签
  • 归档
服务器
  • Java核心基础

    • 基础篇

    • 集合类

    • JVM虚拟机

    • Java并发

      • 产品经理问我:手动创建线程不香吗,为什么非要用线程池呢?
      • 面试官:我问的是Java内存模型,你回答堆栈方法区干嘛?
      • 指令重排序、内存屏障很难?看完这篇你就懂了!
      • Volatile只会用不知道原理?一篇文章带你深究volatile
      • 有关synchronized锁的知识点,我用一篇文章总结了
      • 面试被问AQS、ReentrantLock答不出来?这些知识点让我和面试官聊了半小时!
      • 大厂面试题:你知道JUC中的Semaphore、CyclicBarrier、CountDownLatch吗
        • (一)概述
        • (二)Semaphore
        • (三)Semaphore原理
          • 3.1 默认非公平锁
          • 3.2 Semaphore源码分析
        • (四)CountDownLatch
        • (五)CyclicBarrier
        • (六)总结
      • 关于ThreadLocal的九个知识点,看完别再说不懂了!
  • 框架的艺术

  • 分布式与微服务

  • 开发经验大全

  • 版本新特性

  • Java
  • Java核心基础
  • Java并发
CodeEase
2023-09-19
目录

大厂面试题:你知道JUC中的Semaphore、CyclicBarrier、CountDownLatch吗

作者:鱼仔
博客首页: codeease.top (opens new window)
公众号:Java鱼仔

# (一)概述

资源的分配方式有两种,一种是独占,比如之前讲的ReentrantLock,另外一种是共享,即我们今天将要学习的Semaphore、CyclicBarrier以及CountDownLatch。这些都是JUC包中的类。

# (二)Semaphore

Semaphore是信号量的意思,作用是控制访问特定资源的线程数量。 其核心API为:

semaphore.acquire();
semaphore.release();
1
2

这么说可能比较模糊,下面我举个例子。

Semaphore就好比游乐园中的某个游乐设施的管理员,用来控制同时玩这个游乐设施的人数。比如跳楼机只能坐十个人,就设置Semaphore的permits等于10。

每当有一个人来时,首先判断permits是否大于0,如果大于0,就把一个许可证给这个人,同时自己的permits数量减一。

如果permits数量等于0了,其他人再想进来时就只能排队了。

当一个人玩好之后,这个人把许可证还给Semaphore,permits加1,正在排队的人再来竞争这一个许可证。

下面通过代码来演示这样一个场景

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();
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

在这里设置Semaphore的permit等于2,表示同时只有两个线程可以执行,然后开五个线程,在执行前通过semaphore.acquire();获取permit,执行后通过semaphore.release();归还permit。

7-1.png

通过结果可以观察到,每次最多只会有两个线程执行PlayGame 。

# (三)Semaphore原理

# 3.1 默认非公平锁

Semaphore默认创建的是一个非公平锁:

7-2.png

# 3.2 Semaphore源码分析

Semaphore的实现方式和ReentrantLock十分类似。

首先定义一个内部类Sync继承AbstractQueuedSynchronizer

7-3.png

从Sync的构造方法中可以看到,初始化时设置state等于permits,在讲ReentrantLock的时候,state用来存储重入锁的次数,在Semaphore中state用来存储资源的数量。

Semaphore的核心方法是acquire和release,当执行acquire方法时,sync会执行一个获取一个共享资源的操作:

7-4.png

核心是判断剩余数量是否大于0,如果是的话就通过cas操作去获取资源,否则就进入队列中等待

当执行release方法时,sync会执行一个将一个共享资源放回去的cas操作

7-5.png

# (四)CountDownLatch

countdownlatch能够让一个线程等待其他线程工作完成之后再执行。

countdownlatch通过一个计数器来实现,初始值是指定的数量,每当一个线程完成自己的任务后,计数器减一,当计数器为0时,执行最后的等待线程。

其核心API为

CountDownLatch.countDown();
CountDownLatch.await();
1
2

下面来看代码示例:

设定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();
                }
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

效果如下:

7-6.png

# (五)CyclicBarrier

栅栏屏障,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。 其核心API为:

cyclicBarrier.await();
1

和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();
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

结果如下:

7-7.png

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();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# (六)总结

归根结底,Semaphore、CyclicBarrier、CountDownLatch三个类都是对AQS中资源共享的应用,学懂AQS之后,你会发现JUC包中的类变得不难了。好了,我们下期再见!

上次更新: 2025/04/29, 17:22:06
面试被问AQS、ReentrantLock答不出来?这些知识点让我和面试官聊了半小时!
关于ThreadLocal的九个知识点,看完别再说不懂了!

← 面试被问AQS、ReentrantLock答不出来?这些知识点让我和面试官聊了半小时! 关于ThreadLocal的九个知识点,看完别再说不懂了!→

最近更新
01
AI大模型部署指南
02-18
02
半个月了,DeepSeek为什么还是服务不可用
02-13
03
Python3.9及3.10安装文档
01-23
更多文章>
Theme by Vdoing | Copyright © 2023-2025 备案图标 浙公网安备33021202002405 | 浙ICP备2023040452号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式