Java:多线程计数

本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记

1. CountDownLatch

概念

让一些线程阻塞直到另一些线程完成一系列操作才被唤醒

CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,调用线程就会被阻塞。其它线程调用CountDown 方法会将计数器减1(调用 CountDown 方法的线程不会被阻塞),当计数器的值变成零时,因调用await 方法被阻塞的线程会被唤醒,继续执行。

场景

现在有这样一个场景,假设一个自习室里有7个人,其中有一个是班长,班长的主要职责就是在其它6个同学走了后,关灯,锁教室门,然后走人,因此班长是需要最后一个走的,那么有什么方法能够控制班长这个线程是最后一个执行,而其它线程是随机执行的。

解决方案

这个时候就用到了 CountDownLatch,计数器了。我们一共创建6个线程,然后计数器的值也设置成6

  1. // 计数器
  2. CountDownLatch countDownLatch = new CountDownLatch(6);

然后每次学生线程执行完,就让计数器的值减1

  1. for (int i = 0; i <= 6; i++) {
  2. new Thread(() -> {
  3. System.out.println(Thread.currentThread().getName() + "\t 上完自习,离开教室");
  4. countDownLatch.countDown();
  5. }, String.valueOf(i)).start();
  6. }

最后我们需要通过 CountDownLatch 的 await 方法来控制班长主线程的执行,这里 countDownLatch.await() 可以想成是一道墙,只有当计数器的值为0的时候,墙才会消失,主线程才能继续往下执行

  1. countDownLatch.await();
  2. System.out.println(Thread.currentThread().getName() + "\t 班长最后关门");

不加 CountDownLatch 的执行结果,我们发现 main 线程提前已经执行完成了

  1. 1 上完自习,离开教室
  2. 0 上完自习,离开教室
  3. main 班长最后关门
  4. 2 上完自习,离开教室
  5. 3 上完自习,离开教室
  6. 4 上完自习,离开教室
  7. 5 上完自习,离开教室
  8. 6 上完自习,离开教室

引入 CountDownLatch 后的执行结果,我们能够控制住 main 方法的执行,这样能够保证前提任务的执行

  1. 0 上完自习,离开教室
  2. 2 上完自习,离开教室
  3. 4 上完自习,离开教室
  4. 1 上完自习,离开教室
  5. 5 上完自习,离开教室
  6. 6 上完自习,离开教室
  7. 3 上完自习,离开教室
  8. main 班长最后关门

完整代码

  1. import java.util.concurrent.CountDownLatch;
  2. public class CountDownLatchDemo {
  3. public static void main(String[] args) throws InterruptedException {
  4. // 计数器
  5. CountDownLatch countDownLatch = new CountDownLatch(6);
  6. for (int i = 0; i <= 6; i++) {
  7. new Thread(() -> {
  8. System.out.println(Thread.currentThread().getName() + "\t 上完自习,离开教室");
  9. countDownLatch.countDown();
  10. }, String.valueOf(i)).start();
  11. }
  12. countDownLatch.await();
  13. System.out.println(Thread.currentThread().getName() + "\t 班长最后关门");
  14. }
  15. }

2. CyclicBarrier

概念

和 CountDownLatch 相反,需要集齐七颗龙珠,召唤神龙。也就是做加法,开始是0,加到某个值的时候就执行

CyclicBarrier 的字面意思就是可循环(cyclic)使用的屏障(Barrier)。它要求做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过 CyclicBarrier 的 await 方法

案例

集齐7个龙珠,召唤神龙的 Demo,我们需要首先创建 CyclicBarrier

  1. /**
  2. * 定义一个循环屏障,参数1:需要累加的值,参数2 需要执行的方法
  3. */
  4. CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
  5. System.out.println("召唤神龙");
  6. });

然后同时编写七个线程,进行龙珠收集,但一个线程收集到了的时候,我们需要让他执行await方法,等待到7个线程全部执行完毕后,我们就执行原来定义好的方法

  1. for (int i = 0; i < 7; i++) {
  2. final Integer tempInt = i;
  3. new Thread(() -> {
  4. System.out.println(Thread.currentThread().getName() + "\t 收集到 第" + tempInt + "颗龙珠");
  5. try {
  6. // 先到的被阻塞,等全部线程完成后,才能执行方法
  7. cyclicBarrier.await();
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. } catch (BrokenBarrierException e) {
  11. e.printStackTrace();
  12. }
  13. }, String.valueOf(i)).start();
  14. }

完整代码

  1. /**
  2. * CyclicBarrier循环屏障
  3. */
  4. public class CyclicBarrierDemo {
  5. public static void main(String[] args) {
  6. /**
  7. * 定义一个循环屏障,参数1:需要累加的值,参数2 需要执行的方法
  8. */
  9. CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
  10. System.out.println("召唤神龙");
  11. });
  12. for (int i = 0; i < 7; i++) {
  13. final Integer tempInt = i;
  14. new Thread(() -> {
  15. System.out.println(Thread.currentThread().getName() + "\t 收集到 第" + tempInt + "颗龙珠");
  16. try {
  17. // 先到的被阻塞,等全部线程完成后,才能执行方法
  18. cyclicBarrier.await();
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. } catch (BrokenBarrierException e) {
  22. e.printStackTrace();
  23. }
  24. }, String.valueOf(i)).start();
  25. }
  26. }
  27. }

运行结果:

  1. 1 收集到 1颗龙珠
  2. 0 收集到 0颗龙珠
  3. 2 收集到 2颗龙珠
  4. 3 收集到 3颗龙珠
  5. 4 收集到 4颗龙珠
  6. 5 收集到 5颗龙珠
  7. 6 收集到 6颗龙珠
  8. 召唤神龙

3. Semaphore

概念

信号量主要用于两个目的

  • 一个是用于共享资源的互斥使用
  • 另一个用于并发线程数的控制

代码

我们模拟一个抢车位的场景,假设一共有6个车,3个停车位

那么我们首先需要定义信号量为3,也就是3个停车位

  1. /**
  2. * 初始化一个信号量为3,默认是false 非公平锁, 模拟3个停车位
  3. */
  4. Semaphore semaphore = new Semaphore(3, false);

然后我们模拟6辆车同时并发抢占停车位,但第一个车辆抢占到停车位后,信号量需要减1

  1. // 代表一辆车,已经占用了该车位
  2. semaphore.acquire(); // 抢占

同时车辆假设需要等待3秒后,释放信号量

  1. // 每个车停3秒
  2. try {
  3. TimeUnit.SECONDS.sleep(3);
  4. } catch (InterruptedException e) {
  5. e.printStackTrace();
  6. }

最后车辆离开,释放信号量

  1. // 释放停车位
  2. semaphore.release();

完整代码

  1. /**
  2. * 信号量Demo
  3. */
  4. public class SemaphoreDemo {
  5. public static void main(String[] args) {
  6. /**
  7. * 初始化一个信号量为3,默认是false 非公平锁, 模拟3个停车位
  8. */
  9. Semaphore semaphore = new Semaphore(3, false);
  10. // 模拟6部车
  11. for (int i = 0; i < 6; i++) {
  12. new Thread(() -> {
  13. try {
  14. // 代表一辆车,已经占用了该车位
  15. semaphore.acquire(); // 抢占
  16. System.out.println(Thread.currentThread().getName() + "\t 抢到车位");
  17. // 每个车停3秒
  18. try {
  19. TimeUnit.SECONDS.sleep(3);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. System.out.println(Thread.currentThread().getName() + "\t 离开车位");
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. } finally {
  27. // 释放停车位
  28. semaphore.release();
  29. }
  30. }, String.valueOf(i)).start();
  31. }
  32. }
  33. }

运行结果:

  1. 0 抢到车位
  2. 2 抢到车位
  3. 1 抢到车位
  4. 2 离开车位
  5. 1 离开车位
  6. 3 抢到车位
  7. 0 离开车位
  8. 4 抢到车位
  9. 5 抢到车位
  10. 4 离开车位
  11. 3 离开车位
  12. 5 离开车位

看运行结果能够发现,0 2 1 车辆首先抢占到了停车位,然后等待3秒后,离开,然后后面 3 4 5 又抢到了车位

Java:多线程计数的更多相关文章

  1. Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock

    本章对ReentrantLock包进行基本介绍,这一章主要对ReentrantLock进行概括性的介绍,内容包括:ReentrantLock介绍ReentrantLock函数列表ReentrantLo ...

  2. Java多线程系列--“JUC锁”01之 框架

    本章,我们介绍锁的架构:后面的章节将会对它们逐个进行分析介绍.目录如下:01. Java多线程系列--“JUC锁”01之 框架02. Java多线程系列--“JUC锁”02之 互斥锁Reentrant ...

  3. java多线程系类:JUC线程池:03之线程池原理(二)(转)

    概要 在前面一章"Java多线程系列--"JUC线程池"02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包 ...

  4. java多线程系类:JUC锁:01之框架

    本章,我们介绍锁的架构:后面的章节将会对它们逐个进行分析介绍.目录如下:01. Java多线程系列--"JUC锁"01之 框架02. Java多线程系列--"JUC锁&q ...

  5. Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例

    概要 前面对"独占锁"和"共享锁"有了个大致的了解:本章,我们对CountDownLatch进行学习.和ReadWriteLock.ReadLock一样,Cou ...

  6. Java多线程系列--“JUC锁”06之 Condition条件

    概要 前面对JUC包中的锁的原理进行了介绍,本章会JUC中对与锁经常配合使用的Condition进行介绍,内容包括:Condition介绍Condition函数列表Condition示例转载请注明出处 ...

  7. Java多线程系列--“JUC锁”08之 共享锁和ReentrantReadWriteLock

    概要 Java的JUC(java.util.concurrent)包中的锁包括"独占锁"和"共享锁".在“Java多线程系列--“JUC锁”02之 互斥锁Ree ...

  8. Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例

    概要 本章,我们对JUC包中的信号量Semaphore进行学习.内容包括:Semaphore简介Semaphore数据结构Semaphore源码分析(基于JDK1.7.0_40)Semaphore示例 ...

  9. Java多线程系列--“JUC集合”04之 ConcurrentHashMap

    概要 本章是JUC系列的ConcurrentHashMap篇.内容包括:ConcurrentHashMap介绍ConcurrentHashMap原理和数据结构ConcurrentHashMap函数列表 ...

随机推荐

  1. Appium自动化(3) - adb无线连接手机的方法

    如果你还想从头学起Appium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1693896.html 前言 除了USB方式连接Andro ...

  2. Linux - 解决使用 apt-get 安装 yum 的时耗报 E: Unable to locate package yum 的错误

    问题背景 在 Linux 系统下使用 apt-get 命令安装 yum 库报错 apt-get install yum E: Unable to locate package yum 问题解决 一行命 ...

  3. Docker(42)- 镜像原理之联合文件系统

    前言 学习狂神老师的 Docker 系列课程,并总结 镜像是什么 镜像是一种轻量级.可执行的独立软件保,用来打包软件运行环境和基于运行环境开发的软件 他包含运行某个软件所需的所有内容,包括代码.运行时 ...

  4. DataGridView高度自动调整

    AutoResizeGrid.cs代码 /// <summary> /// 根据行数据,自动调整DataGridView高度 /// </summary> public sea ...

  5. Weblogic Coherence组件漏洞初探CVE-2020-2555

    Weblogic Coherence组件漏洞初探CVE-2020-2555 2020年1月,互联网上爆出了weblogic反序列化远程命令执行漏洞(CVE-2020-2555),Oracle Fusi ...

  6. IIS中配置WCF站点

    http://msdn.microsoft.com/zh-cn/library/aa751852.aspx http://blog.csdn.net/hsg77/article/details/389 ...

  7. String底层使用是char数组还是byte数组

    结论:jdk1.8及以前String底层使用是char[],1.9开始使用byte[] jdk1.8 jdk13

  8. java循环结构、数组

    数组 数组是是多个相同类型数据按一定顺序排列的集合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理. 数组本身是引用数据类型,既可以存储基本数据类型,也可以存储引用数据类型.它的元素相当于 ...

  9. PHP中环境变量的操作

    在 PHP 中,我们可以通过 phpinfo() 查看到当前系统中的环境变量信息(Environment).在代码中,我们也可以通过两个函数,查看和修改相应的环境变量信息. getenv() 获取环境 ...

  10. Go学习【02】:理解Gin,搭一个web demo

    Go Gin 框架 说Gin是一个框架,不如说Gin是一个类库或者工具库,其包含了可以组成框架的组件.这样会更好理解一点. 举个 下面的示例代码在这:github 利用Gin组成最基本的框架.说到框架 ...