Java:多线程计数

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

1. CountDownLatch

概念

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

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

场景

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

解决方案

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

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

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

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

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

countDownLatch.await();

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

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

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

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

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

完整代码

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException { // 计数器
CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 0; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 上完自习,离开教室");
countDownLatch.countDown();
}, String.valueOf(i)).start();
} countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "\t 班长最后关门");
}
}

2. CyclicBarrier

概念

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

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

案例

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

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

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

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

完整代码

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

运行结果:

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

3. Semaphore

概念

信号量主要用于两个目的

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

代码

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

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

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

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

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

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

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

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

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

完整代码

/**
* 信号量Demo
*/
public class SemaphoreDemo { public static void main(String[] args) { /**
* 初始化一个信号量为3,默认是false 非公平锁, 模拟3个停车位
*/
Semaphore semaphore = new Semaphore(3, false); // 模拟6部车
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
// 代表一辆车,已经占用了该车位
semaphore.acquire(); // 抢占
System.out.println(Thread.currentThread().getName() + "\t 抢到车位");
// 每个车停3秒
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t 离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放停车位
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}

运行结果:

0	 抢到车位
2 抢到车位
1 抢到车位
2 离开车位
1 离开车位
3 抢到车位
0 离开车位
4 抢到车位
5 抢到车位
4 离开车位
3 离开车位
5 离开车位

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

最新文章

  1. DOM加载过程中ready和load的区别
  2. linux下memcached的安装
  3. 【poj3709】 K-Anonymous Sequence
  4. [moka同学笔记]linux服务器防火墙的设置
  5. SQL对字符串数组的处理
  6. [官方说明] 为什么ES4要分成两阶段?
  7. 2817 Tangent的愤怒 - Wikioi
  8. uboot的jumptable_init函数分析
  9. ECMAScript版本号总结
  10. zabbix基本操作
  11. 在Docker中运行asp.net core 跨平台应用程序
  12. 质量管理:PDCA循环
  13. 如何开发AR增强现实应用与产品
  14. 解决:Using where; Using join buffer (Block Nested Loop)
  15. php小程序登录时解密getUserInfo获取openId和unionId等敏感信息
  16. sutdio中替换全局方法
  17. SVN diff 笔记
  18. 随机梯度下降算法求解SVM
  19. 【Beanstalkd】Beanstalkd消息队列的安装与使用
  20. 1.ehcache实现页面整体缓存和页面局部缓存

热门文章

  1. 20210805 noip31
  2. 20210718 noip19
  3. WPF WPF中解决内存泄露的几点提示与解决方法
  4. 手把手教你如何玩转消息中间件(ActiveMQ)
  5. pyhton锁机制,进程池
  6. zt:我使用过的Linux命令之ar - 创建静态库.a文件
  7. 字符串出现的topK问题
  8. ARP-NAT(MAC Address Translation)的原理
  9. 关于buildroot移植的思考
  10. C# 将PPT转为OFD/DPT/DPS/ODP/POTX/UOP