【Java多线程】有4个线程分别获取C、D、E、F盘的大小,第5个线程统计总大小

    科技2022-07-10  116

    在《Java多线程编程核心技术》有这样一道题,同时也是某公司(大华)的一道笔试题目。

    文章目录

    1. 题目描述2. 思路分析2.1 为什么会想到用CountDownLatch2.2 CountDownLatch应用场景2.3 为什么不用CyclicBarrier / Semaphore 3.代码实现

    1. 题目描述

    假如有Thread1、Thread2、Thread3、Thread4四条线程分别统计C、D、E、F四个盘的大小,所有线程都统计完毕交给Thread5线程去做汇总,应当如何实现?

    DiskMemory类如下

    public class DiskMemory { // 记录磁盘的总大小 private int totalSize; // 获取一个磁盘的大小,采用随机数生成 public int getSize() { //加一是为了防止获取磁盘大小为0,不符合常理 return (new Random().nextInt(3) + 1) * 100; } //统计磁盘空间的大小 public synchronized void setSize(int size) { totalSize += size; } }

    根据给出的代码,补全剩余代码,实现该功能。

    2. 思路分析

    这主要考察的是线程的通信机制,多个线程共同协作完成任务

    【第一种方法】

    直接用join把线程5加入进去即可

    【第二种方法】

    用juc包下提供的辅助工具类解决

    用CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行


    2.1 为什么会想到用CountDownLatch

    【分析关键字】

    等待:线程5等待前四条线程完成任务后,才开始执行执行一次:每个线程任务只执行一次,共统计四次,四次结束后统计线程停止执行;累加线程开始执行

    CountDownLatch 是计数器,一个线程完成自己的线程任务,计数器就会减一(原子操作)。直到计数器减为0时,被阻塞的线程被唤醒执行。

    countDown:原子类操作,计数器减一 await:调用该方法的线程被阻塞,直到计数器为0时被唤醒执行

    CountDownLatch类是一个同步倒数计数器,构造时传入int参数,该参数就是计数器的初始值,每调用一次countDown()方法,计数器减1,计数器大于0 时, await()方法会阻塞后面程序执行,直到计数器为0,后面被阻塞的方法才会得以实行。await(long timeout, TimeUnitunit),是等待一定时间,然后执行,不管计数器是否到0了。

    2.2 CountDownLatch应用场景

    在某些业务场景中,程序执行需要等待某个条件完成后才能继续执行后续的操作

    【多任务下并行计算】:当某个处理的运算量很大时,可以将该运算任务拆分成多个子任务,等待所有的子任务都完成之后,父任务再拿到所有子任务的运算结果进行汇总

    2.3 为什么不用CyclicBarrier / Semaphore

    CyclicBarrier :同样是计数器,但是此计数器可被重置。CountDownLatch不可被重置。CyclicBarrier 更实用于多个线程之间的等待,直到所有线程满足条件后,才执行后续操作。类比王者荣耀或者CS等游戏,所有玩家进入到游戏房间后游戏才开始。

    Semaphore :是信号量,它主要是用来限流。用来控制访问共享资源的线程数,控制同一时间并发线程的数目。比如说数据库连接池,资源数与线程数相同,一个线程只能获得一个连接,只能获取到一份连接,并且连接数有上限。

    3.代码实现

    import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @Author: iqqcode * @Date: 2020-10-01 23:09 * @Description: 有4个线程分别获取C、D、E、F盘的大小, 第5个线程统计总大小 */ public class DiskMemory { // 记录磁盘的总大小 private int totalSize; // 获取一个磁盘的大小,采用随机数生成 public int getSize() { //加一是为了防止获取磁盘大小为0,不符合常理 return (new Random().nextInt(3) + 1) * 100; } //统计磁盘空间的大小 public synchronized void setSize(int size) { totalSize += size; } // 获得总磁盘空间的大小 public int getTotalSize() { return totalSize; } public static void main(String[] args) throws InterruptedException { // 创建线程池,初始化大小为 4 ExecutorService service = Executors.newFixedThreadPool(4); CountDownLatch countDownLatch = new CountDownLatch(4); DiskMemory diskMemory = new DiskMemory(); for (int i = 0; i < 4; i++) { service.execute(() -> { int timer = new Random().nextInt(5); try { Thread.sleep(timer * 1000); } catch (InterruptedException e) { e.printStackTrace(); } int diskSize = diskMemory.getSize(); System.out.printf("完成磁盘的统计任务,耗费%d秒. 磁盘大小为%d.\n", timer, diskSize); diskMemory.setSize(diskSize); // 任务完成,计数器减一 countDownLatch.countDown(); System.out.println("count num = " + countDownLatch.getCount()); }); } // 主线程一直被阻塞,直到count的计数器被置为0 countDownLatch.await(); System.out.printf("全部磁盘都统计完成,所有磁盘总大小为 " + ", 【 totalSize = " + diskMemory.getTotalSize() + " 】"); service.shutdown(); } }

    执行结果:

    Processed: 0.011, SQL: 8