talk is cheap,show me the code
JUC 其实是属于线程知识的中高级部分, 所以也是面试中面试官喜欢问的一块知识点, 这块知识也能比较看出一个程序员的功底, 今天笔者就跟大家讲讲 CountDownLatch,CyclicBarrier,Semaphore 这些知识点. 大家在看到这些知识的时候, 千万不要一上来就抠细节, 首先应该弄明白这些知识点时是干什么用的, 用来解决什么问题, 适合应用于什么样的业务场景. 如果这些东西都没搞清楚就学, 那么你学完很快就会忘掉.
之前笔者也发过一些关于 JUC 方面的知识, 但也就是一些 Demo 代码, 并没有细究, 今天就是带领大家: 入虎穴, 得虎子.
闭锁 CountDownLatch
如果把多个线程比喻成运动员, CountDownLatch 相当于枪声发令员, 枪声未响, 所有线程都处于等待状态, 当 CountDownLatch 计数器变为 0 时
相当于枪声发出, 这时所有线程一同奔跑. 与集齐七颗龙珠便可召唤神龙有异曲同工之妙.
- package com.bingo.thread.juc;
- /**
- * Created with IntelliJ IDEA.
- * Description: 倒时计数器(也叫闭锁)
- * User: bingo
- * Date: 2018-11-25
- * Time: 11:16
- */
- import java.util.concurrent.CountDownLatch;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- public class CountDownLatchDemo {
- public static void main(String[]args) throws InterruptedException{
- final CountDownLatch startGate=new CountDownLatch(1);
- final CountDownLatch endGate=new CountDownLatch(5);
- // 线程池
- ExecutorService exce=Executors.newCachedThreadPool();
- // 创建 5 个线程
- for(int i=1;i<=5;i++){
- final int num=i;
- Thread thread =new Thread(new Runnable() {
- public void run() {
- try {
- System.out.println(num+"号选手准备就绪, 等待裁判员哨声响起..");
- // 相当于同步锁 Synchronized 中的 await()方法
- startGate.await();
- try {
- Thread.sleep((long) (Math.random()*10000));
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(num+"号选手到达终点..");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- finally{
- // 相当于同步锁 Synchronized 中的 notify()方法, 区别在于 countDown 需要执行 5 次后才能唤醒 await()
- endGate.countDown();
- }
- }
- });
- exce.execute(thread);
- }
- long start=System.nanoTime();
- System.out.println("裁判员哨声响起..");
- Thread.sleep(10000);
- // 唤醒执行 startGate.await()的线程, 让线程往下执行
- startGate.countDown();
- // 等待被唤醒, 主程序才能继续往下执行, 线程中每次执行 endGate.countDown()就减 1, 当为零的时候, 主程序往下执行
- endGate.await();
- long end=System.nanoTime();
- System.out.println("所有运动员到达终点, 耗时:"+(end-start));
- // 关闭线程池
- exce.shutdown();
- }
- }
运行结果
1 裁判员哨声响起..
21 号选手准备就绪, 等待裁判员哨声响起..
32 号选手准备就绪, 等待裁判员哨声响起..
43 号选手准备就绪, 等待裁判员哨声响起..
54 号选手准备就绪, 等待裁判员哨声响起..
65 号选手准备就绪, 等待裁判员哨声响起..
73 号选手到达终点..
81 号选手到达终点..
94 号选手到达终点..
105 号选手到达终点..
112 号选手到达终点..
12 所有运动员到达终点, 耗时: 17708083042
- Process finished with exit code 0
CountDownLatch 实时系统中的使用场景
实现最大的并行性: 有时我们想同时启动多个线程, 实现最大程度的并行性. 例如, 我们想测试一个单例类. 如果我们创建一个初始计数器为 1 的 CountDownLatch, 并让其他所有线程都在这个锁上等待, 只需要调用一次 countDown()方法就可以让其他所有等待的线程同时恢复执行.
开始执行前等待 N 个线程完成各自任务: 例如应用程序启动类要确保在处理用户请求前, 所有 N 个外部系统都已经启动和运行了.
死锁检测: 一个非常方便的使用场景是你用 N 个线程去访问共享资源, 在每个测试阶段线程数量不同, 并尝试产生死锁.
循环屏障 CyclicBarrier
CyclicBarrier 与 CountDownLatch 非常相似, 但是还是有一定区别. 我们先看代码.
需求: 人们 (线程) 先后到达餐桌上(某个点), 但是不能动筷子(等待), 所有人到齐才可以吃年夜饭(线程到齐才能一同执行)
- package com.bingo.thread.juc;
- import java.util.concurrent.BrokenBarrierException;
- import java.util.concurrent.CyclicBarrier;
- public class CyclicBarrierDemo {
- public static void main(String[] args) {
- final int count = 5;
- final CyclicBarrier barrier = new CyclicBarrier(count, new Runnable() {
- @Override
- public void run() {
- System.out.println("人到齐, 大家一起吃年夜饭!");
- }
- });
- // they do not have to start at the same time...
- for (int i = 0; i < count; i++) {
- new Thread(new Worker(i, barrier)).start();
- }
- }
- }
- class Worker implements Runnable {
- final int id;
- final CyclicBarrier barrier;
- public Worker(final int id, final CyclicBarrier barrier) {
- this.id = id;
- this.barrier = barrier;
- }
- @Override
- public void run() {
- try {
- System.out.println(this.id + "starts to run !");
- Thread.sleep((long) (Math.random() * 10000));
- System.out.println(this.id + "到桌 !");
- this.barrier.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (BrokenBarrierException e) {
- e.printStackTrace();
- }
- }
- }
运行结果
- 10starts to run !
- 22starts to run !
- 31starts to run !
- 43starts to run !
- 54starts to run !
64 到桌 !
73 到桌 !
81 到桌 !
92 到桌 !
100 到桌 !
11 人到齐, 大家一起吃年夜饭!
- Process finished with exit code 0
区别
CountDownLatch 减计数, CyclicBarrier 加计数.
CountDownLatch 是一次性的, CyclicBarrier 可以重用.
信号量 Semaphore
Semaphore 与锁类似, 但是与锁不同的是, Synchronized 是独占式的, 同一时刻只有一个线程能够操作资源, 而 Semaphore 允许指定的多个线程同时操作同个资源. 它通过获取许可, 释放许可来控制多个线程操作资源.
需求: 假如现在网吧有 5 台电脑, 但是现在有 8 个人进入网吧, 同一时间只能有 5 个人上机, 而其中三个人主要等到其他人空出电脑的时候才能上机.
这时 Semaphore 就派上用场了.
- package com.bingo.thread.juc;
- import java.util.concurrent.Semaphore;
- public class SemaphoreDemo {
- public static void main(String[] args) {
- int N = 8; // 学生数
- Semaphore semaphore = new Semaphore(5); // 电脑数目
- for(int i=0;i<N;i++)
- new Worker(i,semaphore).start();
- }
- static class Worker extends Thread{
- private int num;
- private Semaphore semaphore;
- public Worker(int num,Semaphore semaphore){
- this.num = num;
- this.semaphore = semaphore;
- }
- @Override
- public void run() {
- try {
- semaphore.acquire();
- System.out.println("同学"+this.num+"占用一台电脑...");
- Thread.sleep(2000);
- System.out.println("-- 同学"+this.num+"离开电脑");
- semaphore.release();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
运行结果
同学 0 占用一台电脑...
同学 1 占用一台电脑...
同学 2 占用一台电脑...
同学 3 占用一台电脑...
同学 4 占用一台电脑...
-- 同学 0 离开电脑
-- 同学 1 离开电脑
-- 同学 4 离开电脑
-- 同学 3 离开电脑
同学 7 占用一台电脑...
-- 同学 2 离开电脑
同学 6 占用一台电脑...
同学 5 占用一台电脑...
-- 同学 7 离开电脑
-- 同学 5 离开电脑
-- 同学 6 离开电脑
- Process finished with exit code 0
多线程是 java 面试中非常重要一环, 算是重点和难点吧, 特别是需要结合 JVM 一起学习, 所以今后笔者还会继续推出相关博客文章, 也希望读者耐心等待.
来源: https://juejin.im/post/5bfcb99cf265da613e21edd5