【小家java】JUC并发编程工具之CountDownLatch(闭锁)、CyclicBarrier、Semaphore的使用(中)

简介: 【小家java】JUC并发编程工具之CountDownLatch(闭锁)、CyclicBarrier、Semaphore的使用(中)

一家人一起吃饭代码示例:


先定义一些方法,模拟吃饭场景


    public static void fatherToRes() {
        System.out.println("爸爸步行去饭店需要3小时。");
    }
    public static void motherToRes() {
        System.out.println("妈妈挤公交去饭店需要2小时。");
    }
    public static void meToRes() {
        System.out.println("我乘地铁去饭店需要1小时。");
    }
    public static void togetherToEat() {
        System.out.println("一家人到齐了,开始吃饭");
    }


顺序执行:


    public static void main(String[] args) {
        fatherToRes();
        motherToRes();
        meToRes();
        togetherToEat();
    }
输出:
爸爸步行去饭店需要3小时。
妈妈挤公交去饭店需要2小时。
我乘地铁去饭店需要1小时。
一家人到齐了,开始吃饭


我们发现,光集合就花了6个小时。改进版本:


    public static void main(String[] args) {
        new Thread(() -> fatherToRes()).start();
        new Thread(() -> motherToRes()).start();
        new Thread(() -> meToRes()).start();
        togetherToEat();
    }
输出:
爸爸步行去饭店需要3小时。
一家人到齐了,开始吃饭
妈妈挤公交去饭店需要2小时。
我乘地铁去饭店需要1小时。


这个好像也不行,人还没到齐就开饭了。继续改进


    //定义一个变量 必须等于0了才开饭
    private static volatile int i = 3;
    public static void main(String[] args) {
        new Thread(() -> {
            fatherToRes();
            i--;
        }).start();
        new Thread(() -> {
            motherToRes();
            i--;
        }).start();
        new Thread(() -> {
            meToRes();
            i--;
        }).start();
        while (i != 0) {
            //此处一直hole住等待
        }
        togetherToEat();
    }
输出:
爸爸步行去饭店需要3小时。
妈妈挤公交去饭店需要2小时。
我乘地铁去饭店需要1小时。
一家人到齐了,开始吃饭


这个实际上达到了效果。但是,但是while盲等待是对于CPU的消耗太巨大了,我们需要更好的实现方式。(备注:此处用volatile修饰i是有并发问题的,读者可以使用AtomicInteger或者LongAdder改进,此处我就不改了哈)


可以参考:

【小家java】使用volatile关键字来实现内存可见性、实现轻量级锁

【小家java】AtomicLong可以抛弃了,请使用LongAdder代替(或使用LongAccumulator)


最终版本:


    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            fatherToRes();
            latch.countDown();
        }).start();
        new Thread(() -> {
            motherToRes();
            latch.countDown();
        }).start();
        new Thread(() -> {
            meToRes();
            latch.countDown();
        }).start();
        latch.await();
        togetherToEat();
    }
输出:
妈妈挤公交去饭店需要2小时。
爸爸步行去饭店需要3小时。
我乘地铁去饭店需要1小时。
一家人到齐了,开始吃饭


这样子,我们就不用一直hold住cpu,不用盲等了。

CyclicBarrier


CyclicBarrier概念,前言里面已经有所介绍了。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。


   public CyclicBarrier(int parties) {
        this(parties, null);
    }


我们把上面一家人一起吃饭的例子改造一下:


    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(4);
    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            fatherToRes();
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
            }
        }).start();
        new Thread(() -> {
            motherToRes();
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
            }
        }).start();
        new Thread(() -> {
            meToRes();
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
            }
        }).start();
        //主线程也要await
        cyclicBarrier.await();
        togetherToEat();
    }


需要注意的是:

1、main主线程也是一个线程,所以也要await

2、new CyclicBarrier的值是4,而不是3(会有个线程控制不了),也不是5(程序将永远等待,因为没有第五个线程执行await方法,即没有第五个线程到达屏障,所以之前到达屏障的四个线程都不会继续执行。)


CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties, Runnable barrierAction),用于在线程到达屏障时(前提条件也必须是先达到屏障),优先执行barrierAction,方便处理更复杂的业务场景。(比如一家人吃饭,必须永远是爸爸先吃)

private static CyclicBarrier cyclicBarrier = new CyclicBarrier(4, () -> System.out.println("人到齐了,爸爸先动筷子"));
    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            fatherToRes();
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
            }
        }).start();
        new Thread(() -> {
            motherToRes();
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
            }
        }).start();
        new Thread(() -> {
            meToRes();
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
            }
        }).start();
        //主线程也要await
        cyclicBarrier.await();
        togetherToEat();
    }
输出:
爸爸步行去饭店需要3小时。
妈妈挤公交去饭店需要2小时。
我乘地铁去饭店需要1小时。
人到齐了,爸爸先动筷子
一家人到齐了,开始吃饭


应用场景


CyclicBarrier可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。

相关文章
|
1月前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
37 0
|
2月前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
244 6
|
2月前
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
54 2
|
2月前
|
设计模式 安全 Java
Java 多线程并发编程
Java多线程并发编程是指在Java程序中使用多个线程同时执行,以提高程序的运行效率和响应速度。通过合理管理和调度线程,可以充分利用多核处理器资源,实现高效的任务处理。本内容将介绍Java多线程的基础概念、实现方式及常见问题解决方法。
141 0
|
7月前
|
Java C++
关于《Java并发编程之线程池十八问》的补充内容
【6月更文挑战第6天】关于《Java并发编程之线程池十八问》的补充内容
56 5
|
4月前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
6月前
|
安全 Java 开发者
Java中的并发编程:深入理解线程池
在Java的并发编程中,线程池是管理资源和任务执行的核心。本文将揭示线程池的内部机制,探讨如何高效利用这一工具来优化程序的性能与响应速度。通过具体案例分析,我们将学习如何根据不同的应用场景选择合适的线程池类型及其参数配置,以及如何避免常见的并发陷阱。
64 1
|
6月前
|
监控 Java
Java并发编程:深入理解线程池
在Java并发编程领域,线程池是提升应用性能和资源管理效率的关键工具。本文将深入探讨线程池的工作原理、核心参数配置以及使用场景,通过具体案例展示如何有效利用线程池优化多线程应用的性能。
|
5月前
|
Java 数据库
Java中的并发编程:深入理解线程池
在Java的并发编程领域,线程池是提升性能和资源管理的关键工具。本文将通过具体实例和数据,探讨线程池的内部机制、优势以及如何在实际应用中有效利用线程池,同时提出一个开放性问题,引发读者对于未来线程池优化方向的思考。
50 0
|
7月前
|
监控 Java 调度
Java并发编程:深入理解线程池
【6月更文挑战第26天】在Java并发编程的世界中,线程池是提升应用性能、优化资源管理的关键组件。本文将深入探讨线程池的内部机制,从核心概念到实际应用,揭示如何有效利用线程池来处理并发任务,同时避免常见的陷阱和错误实践。通过实例分析,我们将了解线程池配置的策略和对性能的影响,以及如何监控和维护线程池的健康状况。
44 1