【Java 并发编程】CountDownLatch 使用场景示例

简介: 【Java 并发编程】CountDownLatch 使用场景示例

文章目录

I CountDownLatch 使用场景举例

II CountDownLatch 简单线程阻塞示例

III CountDownLatch 多个线程联合阻塞示例



I CountDownLatch 使用场景举例


1. 单个阻塞等待单个线程 : 初始化 CountDownLatch 时 , 设置其计数为 1 , 在线程 A 中调用 await() 阻塞 , 然后在线程 B 中执行操作 , 之后调用 countDown() 方法 , 计数 - 1 , 线程 A 阻塞解除 ;


2. 单个阻塞等待多个线程 : 初始化 CountDownLatch 时 , 设置其计数为 2 , 在线程 A 中调用 await() 阻塞 ; 然后在线程 B 中执行操作 , 调用 countDown() 方法 , 计数 - 1 ; 同时在线程 C 中执行更长时间的操作 , 调用 countDown() 方法 , 计数 - 1 ; 线程 B 和 C 的操作执行完毕后 , 其计数才减为 0 , 此时线程 A 中的阻塞解除 ;


3. 多个线程阻塞等待单个线程 : 多个线程中调用 CountDownLatch 对象 await() 方法阻塞 , 在另外一个线程中将计数 countDown() 为 0 , 这些线程即可执行 ;


4. 多个线程阻塞等待多个线程 : 多个线程中调用 CountDownLatch 对象 await() 方法阻塞 , 在另外多个线程中将计数 countDown() 为 0 , 被阻塞这些线程即可执行 ;


5. CountDownLatch 使用场景 :


① 单线程等待单线程 : 线程 A 阻塞 , 等待线程 B 执行完毕后 , 在执行线程 A 操作 ;

② 单线程等待多线程 : 线程 A 阻塞 , 等待线程 B , C , D 等线程执行完毕 , 在执行线程 A 操作 ;

③ 单线程与多线程互相阻塞 : 线程 B , C , D , 先被 new CountDownLatch ( 1 ) 对象阻塞住 , 在线程 A 中先解除 B , C , D 的阻塞 , 然后 B , C , D 这三个线程才能继续执行 , 线程 A 解除之后 , 马上被 new CountDownLatch ( 3 ) 对象阻塞 , B , C , D 三个线程执行完后 , 每个线程计数减一 , 之后解除线程 A 阻塞 , 继续执行线程 A 的内容 ;

④ 单线程与多线程互相阻塞并设置超时时间 : 在上述 ③ 情况的基础上 , 加上超时等待 , 如果 B , C , D 线程在指定时间内没有执行完毕 , 那么线程 A 也解除阻塞 , 继续向下执行之后的代码 ;



II CountDownLatch 简单线程阻塞示例


1. 代码说明 : 子线程运行后调用 CountDownLatch 的 await 方法阻塞 , 在主线程中调用 countDown 方法将计数减为 0 , 子线程解除阻塞 ;


2. 代码示例 :


import java.util.concurrent.CountDownLatch;
/**
 * 子线程运行后调用 CountDownLatch 的 await 方法阻塞 ,
 *      在主线程中调用 countDown 方法将计数减为 0 , 子线程解除阻塞
 */
public class CountDownLatchDemo {
    public static void main(String[] args) {
        System.out.println("1. 主线程 : 开始运行 , 创建 CountDownLatch 对象初始计数为 1");
        //创建 CountDownLatch 对象 , 初始计数为 1
        CountDownLatch countDownLatch = new CountDownLatch(1);
        System.out.println("2. 主线程 : 创建子线程并运行");
        //创建子线程 , 并设置其 countDownLatch 对象, 运行子线程
        MyThread myThread = new MyThread(countDownLatch);
        myThread.start();
        System.out.println("3. 主线程 : 调用 countDownLatch.countDown() 方法");
        countDownLatch.countDown();
        System.out.println("4. 主线程 : 运行结束");
    }
    static class MyThread extends Thread{
        /**
         * 用于阻塞的 CountDownLatch 对象
         */
        CountDownLatch countDownLatch;
        /**
         * 主线程中传入 CountDownLatch 对象 , 两个线程公用一个该对象
         * @param countDownLatch
         */
        public MyThread(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }
        @Override
        public void run() {
            super.run();
            try {
                System.out.println("1. 子线程 : 开始运行 , 并调用 countDownLatch.await() 方法阻塞");
                //阻塞子线程
                countDownLatch.await();
                System.out.println("2. 子线程 : CountDownLatch 对象计数为 0 , 子线程继续运行并结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


3. 执行结果 :


1. 主线程 : 开始运行 , 创建 CountDownLatch 对象初始计数为 1
2. 主线程 : 创建子线程并运行
3. 主线程 : 调用 countDownLatch.countDown() 方法
1. 子线程 : 开始运行 , 并调用 countDownLatch.await() 方法阻塞
4. 主线程 : 运行结束
2. 子线程 : CountDownLatch 对象计数为 0 , 子线程继续运行并结束



III CountDownLatch 多个线程联合阻塞示例


1. 情景描述 : 运动员赛跑 , 1 个裁判 , 4 个运动员 , 4 个运动员首先等待裁判发令 , 才能开始跑 , 裁判发令后在终点等待 4 个运动员都达到终点后 , 在宣布成绩 ;


2. 线程模型分析 :


① 线程 : 裁判员是一个单独的线程 , 4 个运动员是 4 个独立的线程 ;

② CountDownLatch : 两种 CountDownLatch 对象 , 一个用于阻塞裁判员线程 , 一个用于阻塞运动员线程 ;

③ 运动员线程 : 四个运动员线程一开始运行后 , 马上调用 new CountDownLatch(1) 对象阻塞住 , 不能向后运行 ;

④ 裁判员线程 : 裁判员线程要等四个运动员线程启动后才能执行 , 先调用 countDown 将四个运动员线程取消阻塞 , 然后调用new CountDownLatch(4) 对象 的 await 阻塞 , 每个运动员线程跑到终点后 , 调用 countDown 方法 , 四个运动员全部到达终点后 , 裁判员解除阻塞 , 宣布成绩 ;

3. 代码示例 :


i

mport java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 情景描述 : 运动员赛跑 , 1 个裁判 , 4 个运动员 ,
 *      4 个运动员首先等待裁判发令 , 才能开始跑 ,
 *      裁判发令后在终点等待 4 个运动员都达到终点后 , 在宣布成绩 ;
 *
 * 线程模型分析 :
 *
 * ① 线程 : 裁判员是一个单独的线程 , 4 个运动员是 4 个独立的线程 ;
 * ② CountDownLatch : 两种 CountDownLatch 对象 , 一个用于阻塞裁判员线程 , 一个用于阻塞运动员线程 ;
 * ③ 运动员线程 ( 子线程 ) : 四个运动员线程一开始运行后 , 马上调用 new CountDownLatch(1) 对象阻塞住 , 不能向后运行 ;
 * ④ 裁判员线程 ( 主线程 ) : 裁判员线程要等四个运动员线程启动后才能执行 , 先调用 countDown 将四个运动员线程取消阻塞 ,
 *      然后调用new CountDownLatch(4) 对象 的 await 阻塞 , 每个运动员线程跑到终点后 ,
 *      调用 countDown 方法 , 四个运动员全部到达终点后 , 裁判员解除阻塞 , 宣布成绩 ;
 */
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //用于存储四个运动员的成绩
        int[] grades = new int[4];
        //四个运动员线程的线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //阻塞运动员线程的倒计时锁对象 , 需要裁判员线程解锁
        CountDownLatch runnerLatch = new CountDownLatch(1);
        //阻塞裁判线程的倒计时锁对象 , 需要四个运动员线程解锁
        CountDownLatch judgeLatch = new CountDownLatch(4);
        //创建并执行运动员线程 , 使用线程池机制执行
        for(int i = 0; i < 4; i ++){
            int finalI = i;
            //创建运动员线程
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println( finalI + " 号运动员准备完毕 , 等待裁判员发令");
                        runnerLatch.await();
                        System.out.println( finalI + " 号运动员起跑");
                        //设置运动员成绩 , 这里用一个随机数代替
                        grades[finalI] = (int) (Math.random() * 10000);
                        Thread.sleep(grades[finalI]);
                        //通知裁判员到达终点
                        judgeLatch.countDown();
                        System.out.println( finalI + " 号运动员到达终点");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            //使用线程池调度运行该线程
            executorService.execute(runnable);
        }
        System.out.println("裁判员 : 发令 , 起跑");
        //裁判员线程在运动员准备完毕后 , 解除上述 4 个运动员线程的阻塞 , 即运动员起跑
        runnerLatch.countDown();
        System.out.println("裁判员 : 在终点等待 4 名运动员");
        //裁判员线程阻塞, 等待 4 个运动员线程执行完毕
        judgeLatch.await();
        System.out.println("裁判员 : 运动员全部到达终点成绩为 0 号 : " + grades[0] +
                " , 1 号 : " + grades[1] +
                " , 2 号 : " + grades[2] +
                " , 3 号 : " + grades[3]);
    }
}




4. 执行结果 :


裁判员 : 发令 , 起跑
裁判员 : 在终点等待 4 名运动员
2 号运动员准备完毕 , 等待裁判员发令
2 号运动员起跑
1 号运动员准备完毕 , 等待裁判员发令
1 号运动员起跑
0 号运动员准备完毕 , 等待裁判员发令
0 号运动员起跑
3 号运动员准备完毕 , 等待裁判员发令
3 号运动员起跑
1 号运动员到达终点
2 号运动员到达终点
0 号运动员到达终点
3 号运动员到达终点
裁判员 : 运动员全部到达终点成绩为 0 号 : 5601 , 1 号 : 1763 , 2 号 : 4700 , 3 号 : 9650


目录
相关文章
|
12天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
16天前
|
算法 Java 调度
java并发编程中Monitor里的waitSet和EntryList都是做什么的
在Java并发编程中,Monitor内部包含两个重要队列:等待集(Wait Set)和入口列表(Entry List)。Wait Set用于线程的条件等待和协作,线程调用`wait()`后进入此集合,通过`notify()`或`notifyAll()`唤醒。Entry List则管理锁的竞争,未能获取锁的线程在此排队,等待锁释放后重新竞争。理解两者区别有助于设计高效的多线程程序。 - **Wait Set**:线程调用`wait()`后进入,等待条件满足被唤醒,需重新竞争锁。 - **Entry List**:多个线程竞争锁时,未获锁的线程在此排队,等待锁释放后获取锁继续执行。
50 12
|
13天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
93 2
|
29天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
29天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
50 3
|
10天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
12天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
12天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
13天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
35 3
|
21天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
46 6