【小家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的日均银行流水。

相关文章
|
2月前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
50 0
|
3月前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
354 6
|
3月前
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
60 2
|
3月前
|
设计模式 安全 Java
Java 多线程并发编程
Java多线程并发编程是指在Java程序中使用多个线程同时执行,以提高程序的运行效率和响应速度。通过合理管理和调度线程,可以充分利用多核处理器资源,实现高效的任务处理。本内容将介绍Java多线程的基础概念、实现方式及常见问题解决方法。
193 0
|
Java 网络安全 数据安全/隐私保护
[Java工具] 邮件发送工具
注册邮箱 去163邮箱(或其他邮箱)注册一个邮箱,并开启SMTP授权码。 程序 需要注意的是,由于阿里云服务器不让使用默认的25端口,所以会出现Windows下测试发送邮件成功,Linux服务器下发送邮件却出错的问题(broke pipe、timeout、can not connect等)。
1775 0
|
2天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
35 14
|
5天前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
34 13
|
6天前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
1月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
109 17