Java并发编程:CountDownLatch原理与最佳实践
在当今的软件开发领域,高并发编程已成为一项至关重要的技能。特别是在处理大量用户请求或进行大规模数据处理时,如何有效地管理并发任务,确保系统稳定运行,成为开发者面临的一大挑战。Java作为一种广泛应用于企业级应用开发的语言,提供了丰富的并发编程工具和类库。其中,CountDownLatch类便是其中之一。想象一下,在一个复杂的分布式系统中,多个线程需要按照特定的顺序执行任务。例如,一个在线购物平台在
💡亲爱的技术伙伴们:
你是否正被这些问题困扰——
- ✔️ 投递无数简历却鲜有回音?
- ✔️ 技术实力过硬却屡次折戟终面?
- ✔️ 向往大厂却摸不透考核标准?
我打磨的《 Java高级开发岗面试急救包》正式上线!
- ✨ 学完后可以直接立即以此经验找到更好的工作
- ✨ 从全方面地掌握高级开发面试遇到的各种疑难问题
- ✨ 能写出有竞争力的简历,通过模拟面试提升面试者的面试水平
- ✨ 对自己的知识盲点进行一次系统扫盲
🎯 特别适合:
- 📙急需跳槽的在校生、毕业生、Java初学者、Java初级开发、Java中级开发、Java高级开发
- 📙非科班转行需要建立面试自信的开发者
- 📙想系统性梳理知识体系的职场新人
课程链接:https://edu.csdn.net/course/detail/40731课程介绍如下:
📕我是廖志伟,一名Java开发工程师、《Java项目实战——深入理解大型互联网企业通用技术》(基础篇)、(进阶篇)、(架构篇)、《解密程序员的思维密码——沟通、演讲、思考的实践》作者、清华大学出版社签约作家、Java领域优质创作者、CSDN博客专家、阿里云专家博主、51CTO专家博主、产品软文专业写手、技术文章评审老师、技术类问卷调查设计师、幕后大佬社区创始人、开源项目贡献者。
🍊 Java高并发知识点之CountDownLatch:概述
在当今的软件开发领域,高并发编程已成为一项至关重要的技能。特别是在处理大量用户请求或进行大规模数据处理时,如何有效地管理并发任务,确保系统稳定运行,成为开发者面临的一大挑战。Java作为一种广泛应用于企业级应用开发的语言,提供了丰富的并发编程工具和类库。其中,CountDownLatch类便是其中之一。
想象一下,在一个复杂的分布式系统中,多个线程需要按照特定的顺序执行任务。例如,一个在线购物平台在处理订单时,可能需要先检查库存,然后更新库存信息,最后生成订单。在这个过程中,如果线程之间的执行顺序不当,可能会导致数据不一致或业务逻辑错误。CountDownLatch正是为了解决这类问题而设计的。
CountDownLatch,顾名思义,是一个计数器,用于协调多个线程的执行顺序。它允许一个或多个线程等待其他线程完成某个操作。在Java并发编程中,CountDownLatch类提供了以下两个主要功能:
-
概念:CountDownLatch类通过提供一个计数器,使得线程在执行任务前必须等待其他线程完成。当计数器值减至0时,等待的线程将被释放,继续执行后续任务。
-
作用:CountDownLatch在多线程编程中扮演着重要的角色,它可以帮助开发者实现线程间的同步,确保任务按照预期顺序执行,从而提高系统的稳定性和可靠性。
在本系列文章中,我们将详细介绍CountDownLatch类的使用方法,包括如何创建、初始化和释放CountDownLatch对象,以及在实际应用中如何利用CountDownLatch实现线程间的同步。通过学习CountDownLatch,开发者可以更好地掌握Java并发编程,为构建高效、稳定的系统奠定基础。
接下来,我们将深入探讨CountDownLatch的概念和作用,并通过实际案例展示如何在实际项目中应用CountDownLatch。希望通过对CountDownLatch的学习,能够帮助读者在Java并发编程领域取得更大的进步。
// CountDownLatch 概念定义
public class CountDownLatchExample {
// CountDownLatch 是一个同步辅助类,用于在多个线程之间同步执行。
// 它允许一个或多个线程等待其他线程完成一系列操作。
// CountDownLatch 的核心是一个计数器,这个计数器初始值由构造函数指定。
// 当一个线程完成其任务时,它会调用 countDown() 方法,将计数器减一。
// 当计数器达到零时,所有等待的线程将被释放,继续执行。
// 使用场景
// CountDownLatch 常用于以下场景:
// 1. 并行任务执行:多个线程并行执行任务,主线程等待所有线程完成后继续执行。
// 2. 分阶段任务:任务分为多个阶段,每个阶段由不同的线程执行,主线程等待所有阶段完成后继续执行。
// 与CyclicBarrier的区别
// CountDownLatch 和 CyclicBarrier 都用于线程同步,但它们的工作方式不同。
// CountDownLatch 是一个计数器,计数器减到零时,所有等待的线程才会继续执行。
// CyclicBarrier 是一个同步点,所有线程必须到达这个点才能继续执行,并且这个点可以被重用。
// 与Semaphore的区别
// Semaphore 是一个信号量,用于控制对共享资源的访问。
// CountDownLatch 用于线程同步,而不是资源控制。
// Semaphore 可以设置最大并发数,而 CountDownLatch 没有这个限制。
// 与FutureTask的区别
// FutureTask 是一个可以表示异步计算结果的 Future 对象。
// CountDownLatch 用于线程同步,而不是异步计算。
// FutureTask 通常用于单个线程的异步执行,而 CountDownLatch 用于多个线程的同步。
// 代码示例
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(3); // 初始化计数器为3
// 创建并启动三个线程
new Thread(() -> {
System.out.println("Thread 1 is running");
latch.countDown(); // 线程1完成,计数器减一
}).start();
new Thread(() -> {
System.out.println("Thread 2 is running");
latch.countDown(); // 线程2完成,计数器减一
}).start();
new Thread(() -> {
System.out.println("Thread 3 is running");
latch.countDown(); // 线程3完成,计数器减一
}).start();
try {
latch.await(); // 等待所有线程完成
System.out.println("All threads have finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 最佳实践
// 1. 确保CountDownLatch的计数器初始值正确,避免不必要的等待。
// 2. 使用try-catch块处理InterruptedException,确保线程在等待过程中能够正确响应中断。
// 性能分析
// CountDownLatch 的性能主要取决于线程的数量和任务的复杂度。
// 当线程数量较多时,CountDownLatch 的性能可能会受到影响,因为每个线程都需要执行 countDown() 和 await() 方法。
// 在性能敏感的应用中,应考虑使用其他同步机制,如CyclicBarrier或Semaphore。
概念/功能 | CountDownLatch | CyclicBarrier | Semaphore | FutureTask |
---|---|---|---|---|
定义 | 同步辅助类,用于在多个线程之间同步执行,通过计数器控制线程执行。 | 同步点,线程到达该点后继续执行,可重用。 | 控制对共享资源的访问,设置最大并发数。 | 表示异步计算结果的 Future 对象。 |
核心机制 | 计数器,初始值由构造函数指定,通过 countDown() 和 await() 方法控制线程执行。 | 线程到达同步点,等待所有线程到达后继续执行。 | 信号量,控制对共享资源的访问。 | 异步计算结果的 Future 对象。 |
使用场景 | 1. 并行任务执行;2. 分阶段任务。 | 1. 等待多个线程到达同步点;2. 可重用的同步点。 | 1. 控制对共享资源的访问;2. 设置最大并发数。 | 1. 单个线程的异步执行;2. 表示异步计算结果。 |
区别 | - 计数器减到零时,所有等待的线程才会继续执行。<br>- 用于线程同步,而不是资源控制。<br>- 用于多个线程的同步。 | - 所有线程必须到达同步点才能继续执行。<br>- 同步点可重用。 | - 用于资源控制。<br>- 可以设置最大并发数。 | - 用于单个线程的异步执行。<br>- 用于表示异步计算结果。 |
性能 | - 线程数量较多时,性能可能会受到影响。<br>- 每个线程都需要执行 countDown() 和 await() 方法。 | - 线程数量较多时,性能可能会受到影响。<br>- 每个线程都需要到达同步点。 | - 性能取决于信号量的数量和访问共享资源的线程数量。 | - 性能取决于异步计算任务的复杂度。 |
最佳实践 | 1. 确保计数器初始值正确;2. 使用 try-catch 块处理 InterruptedException。 | - 无 | - 无 | - 无 |
在实际应用中,CountDownLatch 和 CyclicBarrier 都可以用于线程同步,但它们在应用场景和机制上有所不同。CountDownLatch 通过计数器实现线程同步,适用于需要等待多个线程完成特定任务的情况,而 CyclicBarrier 则通过同步点实现线程同步,适用于需要所有线程到达某个特定点后才能继续执行的场景。此外,Semaphore 在控制对共享资源的访问时,可以设置最大并发数,从而避免资源竞争,而 FutureTask 则用于表示异步计算结果,适用于单个线程的异步执行。了解这些同步工具的特性和使用场景,有助于开发者更好地进行并发编程。
// CountDownLatch 的作用
public class CountDownLatchDemo {
// CountDownLatch 的核心作用是允许一个或多个线程等待一组事件完成。
public static void main(String[] args) {
// 创建一个 CountDownLatch,初始值为 5
CountDownLatch latch = new CountDownLatch(5);
// 创建并启动 5 个线程
for (int i = 0; i < 5; i++) {
new Thread(() -> {
// 执行任务
System.out.println(Thread.currentThread().getName() + " 开始执行任务");
// 执行完毕后,计数器减 1
latch.countDown();
}).start();
}
// 主线程等待所有任务完成
try {
latch.await();
System.out.println("所有任务执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CountDownLatch 是 Java 并发编程中的一个重要工具,其核心作用是允许一个或多个线程等待一组事件完成。在上述代码示例中,我们创建了一个 CountDownLatch 对象,并将其初始值设置为 5。这意味着有 5 个线程需要完成它们的工作。
每个线程在执行任务后,都会调用 countDown()
方法,这将使 CountDownLatch 的计数器减 1。当计数器减到 0 时,所有等待的线程将被唤醒,继续执行。
CountDownLatch 的这种特性使其在以下场景中非常有用:
- 分步任务同步:在多个线程需要按顺序执行任务时,CountDownLatch 可以确保每个线程在开始下一个任务之前等待前一个任务完成。
- 线程池管理:在创建线程池时,可以使用 CountDownLatch 来确保所有线程都已经被启动。
- 并发控制:在并发编程中,CountDownLatch 可以用来同步多个线程,确保它们在执行某些操作之前等待特定条件成立。
与 CyclicBarrier 相比,CountDownLatch 只能使用一次,而 CyclicBarrier 可以重复使用。CountDownLatch 主要用于等待一组事件完成,而 CyclicBarrier 主要用于线程之间的同步。
在性能分析方面,CountDownLatch 的性能通常优于其他同步机制,因为它不需要锁定和解锁操作。然而,CountDownLatch 的缺点是它不能保证线程执行的顺序。
最佳实践是,在创建 CountDownLatch 时,确保其初始值与需要等待的事件数量相匹配。此外,在使用 CountDownLatch 时,要确保在所有线程执行完毕后调用 await()
方法,以避免主线程过早退出。
场景描述 | CountDownLatch 作用 | 代码示例关键点 |
---|---|---|
分步任务同步 | 确保多个线程按顺序执行任务,每个线程在开始下一个任务之前等待前一个任务完成 | 创建 CountDownLatch 对象,设置初始值,每个线程执行任务后调用 countDown(),主线程调用 await() 等待所有任务完成 |
线程池管理 | 确保所有线程都已经被启动 | 创建 CountDownLatch 对象,设置初始值等于线程池大小,启动线程后调用 countDown(),主线程调用 await() 确保所有线程启动 |
并发控制 | 同步多个线程,确保它们在执行某些操作之前等待特定条件成立 | 创建 CountDownLatch 对象,设置初始值,线程在特定条件成立前调用 await(),条件成立后调用 countDown() |
与 CyclicBarrier 对比 | CountDownLatch 只能使用一次,CyclicBarrier 可以重复使用 | CountDownLatch 用于等待一组事件完成,CyclicBarrier 用于线程之间的同步 |
性能分析 | CountDownLatch 性能通常优于其他同步机制,因为它不需要锁定和解锁操作 | CountDownLatch 不需要复杂的锁定机制,因此性能较高 |
缺点 | 不能保证线程执行的顺序 | CountDownLatch 不保证线程执行的顺序,线程可能在 await() 调用后立即执行或延迟执行 |
最佳实践 | 确保 CountDownLatch 的初始值与需要等待的事件数量相匹配,并在所有线程执行完毕后调用 await() | 在创建 CountDownLatch 时,确保其初始值正确,并在所有线程执行完毕后调用 await() 避免主线程过早退出 |
在实际应用中,CountDownLatch 在分步任务同步场景中尤为有效。例如,在分布式系统中,多个节点需要依次完成数据处理,此时CountDownLatch 可以确保每个节点在开始下一个数据处理步骤前,必须等待前一个步骤完成。这种同步机制不仅提高了系统的整体效率,还增强了系统的健壮性。然而,需要注意的是,CountDownLatch 并不适用于所有同步场景,特别是在需要保证线程执行顺序的情况下,它可能不是最佳选择。
🍊 Java高并发知识点之CountDownLatch:原理
在当今的软件开发领域,高并发编程已成为一种基本技能。特别是在处理多线程应用时,如何有效地协调线程间的同步与协作,是保证系统稳定性和性能的关键。CountDownLatch作为一种同步工具,在Java并发编程中扮演着重要角色。下面,我们将深入探讨CountDownLatch的原理,并对其内部实现和工作流程进行概述。
在许多实际应用场景中,我们可能会遇到这样的问题:一个任务需要等待多个子任务完成后再继续执行。例如,在分布式系统中,一个主节点需要等待所有从节点完成数据同步后,才能进行下一步操作。在这种情况下,CountDownLatch能够提供一种有效的解决方案。
CountDownLatch的核心思想是,它允许一个或多个线程等待其他线程完成某个操作。它通过一个计数器实现,这个计数器的初始值代表需要等待的线程数量。每当一个线程完成其任务时,它会调用CountDownLatch的countDown()方法,将计数器减一。当计数器值为0时,表示所有线程都已完成,此时等待的线程可以继续执行。
介绍CountDownLatch的原理具有重要意义。首先,它有助于我们理解Java并发编程的基本原理,特别是在多线程同步和协作方面的应用。其次,CountDownLatch在实际开发中具有很高的实用性,能够帮助我们解决许多复杂的多线程问题。
接下来,我们将对CountDownLatch的内部实现和工作流程进行详细阐述。首先,我们将探讨CountDownLatch的内部实现,包括其数据结构和同步机制。然后,我们将介绍CountDownLatch的工作流程,包括初始化、等待和唤醒过程。通过这些内容,读者可以全面了解CountDownLatch的工作原理,为在实际项目中应用CountDownLatch打下坚实基础。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
// 创建一个CountDownLatch实例,初始值为N
private CountDownLatch latch = new CountDownLatch(5);
// 一个简单的任务方法,模拟线程执行任务
public void doWork() {
// 执行任务
System.out.println(Thread.currentThread().getName() + " is doing work.");
// 完成任务后,计数器减1
latch.countDown();
}
// 主方法,模拟多个线程执行任务
public static void main(String[] args) {
CountDownLatchExample example = new CountDownLatchExample();
// 创建并启动5个线程
for (int i = 0; i < 5; i++) {
new Thread(example::doWork).start();
}
// 等待所有线程执行完毕
example.latch.await();
// 所有任务完成后,执行后续操作
System.out.println("All work is done.");
}
}
CountDownLatch是一种线程同步机制,用于等待一组事件发生。它允许一个或多个线程等待,直到其他线程执行完一组操作。CountDownLatch内部实现原理是通过一个共享的计数器来控制线程的执行。
CountDownLatch内部维护一个计数器,初始值为N。每当一个线程完成其任务时,它会调用countDown()
方法,将计数器减1。当计数器减到0时,所有等待的线程将被释放,继续执行。
CountDownLatch的应用场景包括但不限于:
- 并行任务执行:在多个线程并行执行任务时,等待所有任务完成。
- 分阶段任务执行:在分阶段执行任务时,等待前一阶段任务完成。
- 线程池管理:在线程池中,等待所有线程执行完毕。
CountDownLatch与CyclicBarrier的比较:
- CountDownLatch:线程执行完毕后,计数器减1,当计数器为0时,所有等待的线程继续执行。
- CyclicBarrier:线程执行完毕后,等待其他线程,当所有线程到达屏障点时,所有线程继续执行。
CountDownLatch与Semaphore的比较:
- CountDownLatch:用于等待一组事件发生,计数器减到0时,所有等待的线程继续执行。
- Semaphore:用于控制对共享资源的访问,通过获取和释放许可来控制线程的执行。
性能分析:
CountDownLatch的性能取决于线程数量和任务执行时间。在大量线程和长时间任务的情况下,CountDownLatch的性能可能会受到影响。
最佳实践:
- 在使用CountDownLatch时,确保线程执行完毕后调用
countDown()
方法。 - 避免在CountDownLatch中使用共享资源,以避免线程安全问题。
- 在实际应用中,根据需求选择合适的线程同步机制。
对比项 | CountDownLatch | CyclicBarrier | Semaphore |
---|---|---|---|
原理 | 通过共享计数器控制线程执行 | 通过屏障点等待所有线程到达 | 通过许可控制对共享资源的访问 |
计数器初始值 | 由用户指定,表示需要等待的事件数 | 由用户指定,表示线程到达屏障点数 | 由用户指定,表示许可数量 |
线程释放方式 | 计数器减到0时,所有等待线程释放 | 所有线程到达屏障点后释放 | 获取许可后线程继续执行,释放许可后线程等待 |
应用场景 | 并行任务执行、分阶段任务执行、线程池管理 | 并行任务执行、分阶段任务执行、线程池管理 | 控制对共享资源的访问、线程同步 |
性能影响 | 线程数量和任务执行时间影响性能 | 线程数量和任务执行时间影响性能 | 许可数量和资源访问频率影响性能 |
最佳实践 | 确保线程执行完毕后调用countDown() | 避免在屏障点进行长时间操作 | 确保许可数量与资源需求匹配 |
CountDownLatch和CyclicBarrier在并行任务执行中扮演着重要的角色,它们通过不同的机制确保线程同步。CountDownLatch通过共享计数器控制线程执行,当计数器减到0时,所有等待线程释放,适用于分阶段任务执行和线程池管理。而CyclicBarrier则通过屏障点等待所有线程到达,所有线程到达屏障点后释放,适用于并行任务执行和线程池管理。在实际应用中,需要注意避免在屏障点进行长时间操作,以确保性能。
CountDownLatch是一种同步辅助类,在Java并发编程中用于线程间的同步。它允许一个或多个线程等待一组事件发生,直到所有事件都发生后,这些线程才会继续执行。下面将详细阐述CountDownLatch的工作流程。
CountDownLatch的核心思想是利用一个计数器来控制线程的执行。当初始化CountDownLatch时,可以指定一个初始值,这个值表示需要等待的事件数量。每当一个事件发生时,计数器就会减一。当计数器减到零时,表示所有事件都已完成,等待的线程可以继续执行。
🎉 工作原理
CountDownLatch的工作原理如下:
- 初始化:创建CountDownLatch对象时,需要指定一个初始值,表示需要等待的事件数量。
- 等待事件:调用CountDownLatch的await()方法,当前线程会阻塞,直到计数器减到零。
- 事件发生:在某个线程中,每当一个事件发生时,调用CountDownLatch的countDown()方法,计数器减一。
- 继续执行:当计数器减到零时,所有等待的线程都会被唤醒,继续执行。
🎉 线程同步
CountDownLatch通过线程同步机制实现线程间的等待和通知。当线程调用await()方法时,它会阻塞当前线程,直到其他线程调用countDown()方法使计数器减到零。这样,所有线程都会在事件发生前等待,确保事件按顺序执行。
🎉 并发控制
CountDownLatch在并发控制方面具有以下特点:
- 线程安全:CountDownLatch内部使用volatile关键字保证计数器的可见性和原子性。
- 无锁设计:CountDownLatch不依赖于锁机制,避免了锁竞争和死锁问题。
- 高效性:CountDownLatch在等待事件发生时,不会占用太多系统资源。
🎉 使用场景
CountDownLatch适用于以下场景:
- 并行任务执行:在多个线程并行执行任务时,需要等待所有任务完成后,再进行下一步操作。
- 线程池管理:在创建线程池时,可以使用CountDownLatch等待所有线程启动完成。
- 数据同步:在多线程访问共享数据时,可以使用CountDownLatch确保数据同步。
🎉 代码示例
以下是一个使用CountDownLatch的示例:
public class CountDownLatchExample {
public static void main(String[] args) {
int numberOfThreads = 5;
CountDownLatch latch = new CountDownLatch(numberOfThreads);
for (int i = 0; i < numberOfThreads; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " started.");
Thread.sleep(1000);
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
try {
System.out.println("Waiting for all threads to finish...");
latch.await();
System.out.println("All threads finished.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
🎉 性能分析
CountDownLatch的性能主要取决于以下因素:
- 线程数量:线程数量越多,等待时间越长。
- 事件数量:事件数量越多,计数器减到零所需时间越长。
- 系统资源:系统资源(如CPU、内存)充足时,CountDownLatch的性能越好。
🎉 与CyclicBarrier比较
CountDownLatch和CyclicBarrier都是线程同步工具,但它们在应用场景上有所不同:
- CountDownLatch:适用于等待一组事件发生,然后继续执行的场景。
- CyclicBarrier:适用于多个线程需要定期同步的场景。
🎉 与其他并发工具类结合使用
CountDownLatch可以与其他并发工具类结合使用,例如:
- ReentrantLock:使用CountDownLatch和ReentrantLock可以实现更复杂的线程同步逻辑。
- Semaphore:使用CountDownLatch和Semaphore可以实现线程池的动态调整。
🎉 最佳实践
- 选择合适的初始值:根据实际需求选择合适的初始值,避免浪费资源。
- 避免死锁:确保所有线程都能成功调用countDown()方法,否则可能导致死锁。
- 释放资源:在CountDownLatch不再使用时,及时释放资源,避免内存泄漏。
特征 | CountDownLatch |
---|---|
核心思想 | 利用计数器控制线程执行,等待一组事件发生后再继续执行。 |
工作原理 | 初始化计数器,调用await()方法等待,事件发生时调用countDown()方法减一,计数器为零时唤醒线程。 |
线程同步 | 通过线程同步机制实现等待和通知,确保事件按顺序执行。 |
并发控制 | 线程安全,无锁设计,高效性高。 |
使用场景 | 并行任务执行、线程池管理、数据同步等。 |
性能分析 | 线程数量、事件数量、系统资源等因素影响性能。 |
与CyclicBarrier比较 | CountDownLatch适用于等待一组事件发生,CyclicBarrier适用于定期同步。 |
与其他并发工具类结合使用 | 可与ReentrantLock、Semaphore等结合使用,实现更复杂的线程同步逻辑。 |
最佳实践 | 选择合适的初始值,避免死锁,及时释放资源。 |
CountDownLatch在多线程编程中扮演着重要的角色,它通过设置一个计数器,使得线程在执行前必须等待其他线程完成特定的事件。这种机制在实现并行任务执行时尤为有效,例如,在分布式系统中,多个节点需要完成各自的任务后,主节点才能进行下一步操作。CountDownLatch的灵活运用,不仅提高了程序的执行效率,还降低了资源消耗,是现代并发编程中不可或缺的工具之一。
🍊 Java高并发知识点之CountDownLatch:使用方法
在大型分布式系统中,高并发处理是保证系统性能的关键。Java作为主流的编程语言之一,提供了丰富的并发工具来应对高并发场景。CountDownLatch是Java并发包中的一个同步辅助类,它允许一个或多个线程等待其他线程完成操作。下面,我们将深入探讨CountDownLatch的使用方法。
在分布式系统中,我们经常遇到这样一个场景:一个主线程需要等待多个子线程完成各自的任务后,才能继续执行后续操作。例如,在一个分布式任务处理系统中,主线程负责将任务分发到各个子线程进行处理,只有当所有任务都处理完毕后,主线程才能进行结果汇总。这时,CountDownLatch就派上了用场。
CountDownLatch的使用方法主要包括以下几个方面:
-
创建与初始化:首先,需要创建一个CountDownLatch对象,并指定初始计数。这个计数表示需要等待的线程数量。
-
await方法:当子线程完成自己的任务后,调用await方法。如果CountDownLatch的计数大于0,当前线程将被阻塞,直到计数减为0。
-
countDown方法:在子线程完成任务后,调用countDown方法,将CountDownLatch的计数减1。当计数减为0时,所有等待的线程将被唤醒。
介绍CountDownLatch的使用方法具有重要意义。首先,它能够简化并发编程的复杂性,使得开发者能够更加专注于业务逻辑的实现。其次,CountDownLatch能够提高系统的响应速度,因为它允许主线程在子线程完成任务后立即继续执行,从而减少等待时间。最后,CountDownLatch在分布式系统中具有广泛的应用场景,如任务分发、分布式锁等,因此掌握其使用方法对于开发高性能的分布式系统至关重要。
接下来,我们将依次介绍CountDownLatch的创建与初始化、await方法和countDown方法,帮助读者全面了解CountDownLatch的使用方法。
// 创建CountDownLatch实例
CountDownLatch latch = new CountDownLatch(5);
// 初始化CountDownLatch实例
latch.countDown();
在Java并发编程中,CountDownLatch是一个非常有用的同步工具,它允许一个或多个线程等待一组事件完成。下面将详细阐述CountDownLatch的创建、初始化过程。
🎉 创建CountDownLatch实例
创建CountDownLatch实例非常简单,只需使用CountDownLatch
类提供的构造函数即可。构造函数接收一个整数参数,表示计数器的初始值,即需要等待的线程数量。以下是一个创建CountDownLatch实例的示例:
// 创建一个CountDownLatch实例,初始值为5
CountDownLatch latch = new CountDownLatch(5);
在这个例子中,latch
的初始值为5,意味着需要等待5个线程完成。
🎉 初始化CountDownLatch实例
初始化CountDownLatch实例的过程就是调用countDown()
方法。每次调用countDown()
方法,latch
的计数器就会减1。当计数器减到0时,所有等待的线程将被唤醒。以下是一个初始化CountDownLatch实例的示例:
// 初始化CountDownLatch实例
latch.countDown();
在这个例子中,latch
的计数器从5减到4。如果还有其他线程在等待,它们将继续等待;如果所有线程都已经调用过countDown()
方法,那么等待的线程将被唤醒。
需要注意的是,countDown()
方法可以在任何线程中调用,但通常建议在执行任务的线程中调用,以确保计数器正确减到0。
总结一下,CountDownLatch的创建和初始化过程非常简单,只需创建一个实例并调用countDown()
方法即可。在实际应用中,CountDownLatch可以用于协调多个线程的执行,确保它们在特定事件完成后才继续执行。
操作步骤 | 描述 | 示例代码 |
---|---|---|
创建CountDownLatch实例 | 使用CountDownLatch 类构造函数创建实例,并指定需要等待的线程数量。 |
CountDownLatch latch = new CountDownLatch(5); |
初始化CountDownLatch实例 | 调用countDown() 方法减少CountDownLatch 的计数器。 |
latch.countDown(); |
等待事件完成 | 在需要等待的线程中使用await() 方法挂起当前线程,直到计数器减到0。 |
latch.await(); |
唤醒等待线程 | 当计数器减到0时,所有等待的线程将被唤醒,继续执行。 | 无需额外代码,await() 方法自动完成唤醒操作。 |
使用场景 | CountDownLatch常用于线程间的同步,确保某些线程在特定事件完成后才继续执行。 | 在多线程任务中,确保所有线程完成准备工作后才开始执行核心任务。 |
CountDownLatch在多线程编程中扮演着重要的角色,它能够确保线程在特定事件完成后才继续执行。例如,在执行一个复杂的多线程任务时,可能需要确保所有线程都完成了各自的准备工作,然后才开始执行核心任务。在这种情况下,CountDownLatch可以作为一个同步点,确保所有线程都达到了这个同步点,从而保证任务的正确执行。此外,CountDownLatch还可以用于实现更复杂的线程同步逻辑,例如,在并行计算中,可以用来同步多个计算任务,确保它们在某个特定时刻同时开始或结束。通过合理地使用CountDownLatch,可以有效地提高程序的并发性能和可靠性。
// CountDownLatch 类的 await 方法原理
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// CountDownLatch 类的 await 方法原理分析
// 1. 调用 sync.acquireSharedInterruptibly(1) 方法
// 2. acquireSharedInterruptibly 方法是 AbstractQueuedSynchronizer (AQS) 的一个方法
// 3. AQS 维护一个计数器 state,初始值为 count
// 4. 当调用 acquireSharedInterruptibly 方法时,会检查计数器是否大于 0
// 5. 如果计数器大于 0,则线程会进入等待队列
// 6. 当计数器等于 0 时,等待队列中的线程会依次被唤醒
// 7. 如果线程在等待过程中被中断,则会抛出 InterruptedException
// CountDownLatch 的使用场景
// 1. 等待多个线程完成某个任务
// 2. 等待某个事件发生
// 3. 等待某个资源可用
// CountDownLatch 与 CyclicBarrier 的区别
// 1. CountDownLatch 用于等待多个线程完成某个任务,而 CyclicBarrier 用于等待多个线程到达某个屏障点
// 2. CountDownLatch 的计数器只能减到 0,而 CyclicBarrier 的计数器可以循环使用
// 3. CountDownLatch 不支持线程重入,而 CyclicBarrier 支持线程重入
// CountDownLatch 与 Semaphore 的比较
// 1. CountDownLatch 用于等待多个线程完成某个任务,而 Semaphore 用于控制对共享资源的访问
// 2. CountDownLatch 的计数器只能减到 0,而 Semaphore 的计数器可以任意设置
// 3. CountDownLatch 不支持线程重入,而 Semaphore 支持线程重入
// 代码示例
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
new Thread(() -> {
System.out.println("Thread 1 is running");
latch.countDown();
}).start();
new Thread(() -> {
System.out.println("Thread 2 is running");
latch.countDown();
}).start();
new Thread(() -> {
System.out.println("Thread 3 is running");
latch.countDown();
}).start();
latch.await();
System.out.println("All threads have finished");
}
}
// 最佳实践
// 1. 在使用 CountDownLatch 时,确保计数器的值与线程数一致
// 2. 在使用 CountDownLatch 时,避免在 await 方法中执行耗时操作
// 3. 在使用 CountDownLatch 时,注意异常处理
// 性能影响
// 1. CountDownLatch 的性能取决于线程数和任务复杂度
// 2. 当线程数较多时,CountDownLatch 的性能可能会受到影响
// 线程安全
// 1. CountDownLatch 是线程安全的,因为它基于 AQS 实现
// 2. 在使用 CountDownLatch 时,确保不要在 await 方法中修改共享资源
// 3. 在使用 CountDownLatch 时,注意异常处理,避免线程中断导致的问题
以上代码块展示了 CountDownLatch 的 await 方法原理、使用场景、与 CyclicBarrier 和 Semaphore 的区别、代码示例、最佳实践、性能影响、线程安全和异常处理等内容。
特性/概念 | 描述 |
---|---|
CountDownLatch 的 await 方法原理 | 1. 调用 sync.acquireSharedInterruptibly(1) 方法。2. acquireSharedInterruptibly 方法是 AbstractQueuedSynchronizer (AQS) 的一个方法。3. AQS 维护一个计数器 state ,初始值为 count 。4. 当调用 acquireSharedInterruptibly 方法时,会检查计数器是否大于 0。5. 如果计数器大于 0,则线程会进入等待队列。6. 当计数器等于 0 时,等待队列中的线程会依次被唤醒。7. 如果线程在等待过程中被中断,则会抛出 InterruptedException 。 |
CountDownLatch 的使用场景 | 1. 等待多个线程完成某个任务。2. 等待某个事件发生。3. 等待某个资源可用。 |
CountDownLatch 与 CyclicBarrier 的区别 | 1. CountDownLatch 用于等待多个线程完成某个任务,而 CyclicBarrier 用于等待多个线程到达某个屏障点。2. CountDownLatch 的计数器只能减到 0,而 CyclicBarrier 的计数器可以循环使用。3. CountDownLatch 不支持线程重入,而 CyclicBarrier 支持线程重入。 |
CountDownLatch 与 Semaphore 的比较 | 1. CountDownLatch 用于等待多个线程完成某个任务,而 Semaphore 用于控制对共享资源的访问。2. CountDownLatch 的计数器只能减到 0,而 Semaphore 的计数器可以任意设置。3. CountDownLatch 不支持线程重入,而 Semaphore 支持线程重入。 |
代码示例 | java public class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(3); new Thread(() -> { System.out.println("Thread 1 is running"); latch.countDown(); }).start(); new Thread(() -> { System.out.println("Thread 2 is running"); latch.countDown(); }).start(); new Thread(() -> { System.out.println("Thread 3 is running"); latch.countDown(); }).start(); latch.await(); System.out.println("All threads have finished"); } } |
最佳实践 | 1. 在使用 CountDownLatch 时,确保计数器的值与线程数一致。2. 在使用 CountDownLatch 时,避免在 await 方法中执行耗时操作。3. 在使用 CountDownLatch 时,注意异常处理。 |
性能影响 | 1. CountDownLatch 的性能取决于线程数和任务复杂度。2. 当线程数较多时,CountDownLatch 的性能可能会受到影响。 |
线程安全 | 1. CountDownLatch 是线程安全的,因为它基于 AQS 实现。2. 在使用 CountDownLatch 时,确保不要在 await 方法中修改共享资源。3. 在使用 CountDownLatch 时,注意异常处理,避免线程中断导致的问题。 |
CountDownLatch 在实际应用中,可以有效地管理线程间的同步,特别是在需要确保所有线程都完成特定任务后才继续执行后续操作的场景中。例如,在分布式系统中,CountDownLatch 可以用来确保所有节点都完成了初始化工作,然后才开始执行后续的分布式计算任务。此外,CountDownLatch 还可以与数据库事务结合使用,确保在所有线程都完成数据更新后,才提交事务,从而提高数据的一致性和完整性。
// CountDownLatch类中的countDown方法示例
public class CountDownLatchExample {
public static void main(String[] args) {
// 创建一个CountDownLatch对象,初始计数为3
CountDownLatch latch = new CountDownLatch(3);
// 创建并启动三个线程
new Thread(() -> {
System.out.println("线程1开始执行");
// 执行任务...
latch.countDown(); // 递减计数
System.out.println("线程1执行完毕");
}).start();
new Thread(() -> {
System.out.println("线程2开始执行");
// 执行任务...
latch.countDown(); // 递减计数
System.out.println("线程2执行完毕");
}).start();
new Thread(() -> {
System.out.println("线程3开始执行");
// 执行任务...
latch.countDown(); // 递减计数
System.out.println("线程3执行完毕");
}).start();
try {
// 等待所有线程执行完毕
latch.await();
System.out.println("所有线程执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CountDownLatch是一个同步辅助类,用于在多个线程之间进行协调。它允许一个或多个线程等待其他线程完成某个操作。CountDownLatch内部维护了一个计数器,初始值为指定值。每当调用countDown方法时,计数器减1。当计数器为0时,表示所有线程都已完成操作,此时等待的线程可以继续执行。
🎉 方法原理
CountDownLatch内部使用一个原子变量(AtomicInteger)来维护计数器的值。countDown方法通过原子变量自减操作来递减计数器的值。当计数器为0时,表示所有线程都已完成操作。
🎉 使用场景
CountDownLatch常用于以下场景:
- 等待多个线程执行完毕后,再执行某个操作。
- 在多个线程中同步操作,确保所有线程都到达某个特定点。
- 在多线程环境中,实现线程间的协作。
🎉 与CyclicBarrier对比
CountDownLatch和CyclicBarrier都是线程同步工具,但它们的使用场景有所不同。
- CountDownLatch用于等待多个线程执行完毕,而CyclicBarrier用于等待所有线程到达某个特定点。
- CountDownLatch只能使用一次,而CyclicBarrier可以多次使用。
🎉 与Semaphore对比
CountDownLatch和Semaphore都是线程同步工具,但它们的作用不同。
- CountDownLatch用于等待多个线程执行完毕,而Semaphore用于控制对共享资源的访问。
- CountDownLatch的计数器只能递减,而Semaphore的许可数可以递增和递减。
🎉 代码示例
以下是一个使用CountDownLatch的示例:
public class CountDownLatchExample {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3);
new Thread(() -> {
System.out.println("线程1开始执行");
// 执行任务...
latch.countDown();
System.out.println("线程1执行完毕");
}).start();
new Thread(() -> {
System.out.println("线程2开始执行");
// 执行任务...
latch.countDown();
System.out.println("线程2执行完毕");
}).start();
new Thread(() -> {
System.out.println("线程3开始执行");
// 执行任务...
latch.countDown();
System.out.println("线程3执行完毕");
}).start();
try {
latch.await();
System.out.println("所有线程执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
🎉 最佳实践
- 在使用CountDownLatch时,确保所有线程都调用了countDown方法,否则可能导致等待线程一直阻塞。
- 在使用CountDownLatch时,注意处理InterruptedException异常。
🎉 性能影响
CountDownLatch的性能取决于线程数量和任务执行时间。当线程数量较多或任务执行时间较长时,CountDownLatch的性能可能会受到影响。
🎉 线程安全
CountDownLatch是线程安全的,因为它使用原子变量来维护计数器的值。
🎉 异常处理
在使用CountDownLatch时,需要注意处理InterruptedException异常。当线程在等待过程中被中断时,会抛出此异常。
特性/概念 | CountDownLatch | CyclicBarrier | Semaphore |
---|---|---|---|
定义 | 一个同步辅助类,允许一个或多个线程等待其他线程完成某个操作。 | 一个同步辅助类,允许一组线程到达一个屏障点,然后所有线程一起执行操作。 | 一个用于控制对共享资源访问的线程同步工具。 |
计数器 | 原子变量(AtomicInteger)维护计数器的值。 | 原子变量(AtomicInteger)维护计数器的值。 | 许可证(许可数)可以递增和递减。 |
使用场景 | 等待多个线程执行完毕后,再执行某个操作。 | 等待所有线程到达某个特定点,然后所有线程一起执行操作。 | 控制对共享资源的访问,例如,限制同时访问资源的线程数量。 |
计数器操作 | countDown方法递减计数器。当计数器为0时,等待的线程可以继续执行。 | await方法使线程到达屏障点,所有线程等待。 barrier方法使线程离开屏障点,所有线程继续执行。 | acquire方法获取许可,release方法释放许可。 |
使用次数 | 只能使用一次。一旦计数器归零,CountDownLatch对象将不再可用。 | 可以多次使用。CyclicBarrier对象在所有线程通过屏障点后重置计数器。 | 可以多次使用。Semaphore对象可以递增和递减许可数。 |
线程安全 | 线程安全,使用原子变量维护计数器的值。 | 线程安全,使用原子变量维护计数器的值。 | 线程安全,使用原子变量维护许可数的值。 |
异常处理 | 在等待过程中,如果线程被中断,会抛出InterruptedException异常。 | 在等待过程中,如果线程被中断,会抛出InterruptedException异常。 | 在尝试获取许可时,如果当前没有许可可用,会抛出InterruptedException异常。 |
性能影响 | 性能取决于线程数量和任务执行时间。当线程数量较多或任务执行时间较长时,性能可能会受到影响。 | 性能取决于线程数量和任务执行时间。当线程数量较多或任务执行时间较长时,性能可能会受到影响。 | 性能取决于线程数量和资源访问频率。当资源访问频率较高时,性能可能会受到影响。 |
CountDownLatch和CyclicBarrier在并发编程中扮演着重要的角色,它们通过原子变量来维护计数器的值,确保线程间的同步。CountDownLatch主要用于等待多个线程完成某个操作后再执行后续操作,而CyclicBarrier则允许一组线程到达一个屏障点,然后所有线程一起执行操作。与CountDownLatch只能使用一次不同,CyclicBarrier可以多次使用,这在需要多次同步的场景中非常有用。Semaphore则用于控制对共享资源的访问,通过递增和递减许可数来管理线程对资源的访问权限。在实际应用中,应根据具体需求选择合适的同步工具,以优化程序性能和资源利用率。
🍊 Java高并发知识点之CountDownLatch:注意事项
在Java高并发编程中,CountDownLatch是一个非常有用的同步工具,它允许一个或多个线程等待一组事件发生。然而,在使用CountDownLatch时,开发者需要特别注意一些事项,以确保程序的稳定性和正确性。
想象一个场景,一个复杂的分布式系统中,多个线程需要协同工作,共同完成一个任务。例如,一个后台任务需要等待所有数据节点处理完毕后,才能进行汇总。如果没有适当的同步机制,可能会导致数据不一致或任务无法正确完成。CountDownLatch正是为了解决这类问题而设计的。
CountDownLatch的注意事项主要包括两个方面:线程安全问题以及适用场景。
首先,关于线程安全问题,CountDownLatch本身是线程安全的,因为它内部维护了一个计数器,用于记录等待的线程数量。但是,在使用CountDownLatch时,开发者需要注意不要在同一个线程中多次调用await()方法,这会导致死锁。此外,当CountDownLatch的计数器被减至0时,所有等待的线程会继续执行,此时如果不当心处理共享资源,可能会引发竞态条件。
其次,CountDownLatch的适用场景主要包括以下几种情况:一是需要等待一组事件发生,如上述分布式系统中的后台任务;二是需要确保某个操作在所有线程完成之前不被执行;三是需要协调多个线程的执行顺序。
接下来,我们将分别对CountDownLatch的线程安全问题以及适用场景进行详细探讨。通过深入了解这些内容,读者可以更好地掌握CountDownLatch的使用方法,并在实际项目中发挥其优势。
CountDownLatch是一种同步辅助类,在Java并发编程中用于线程间的同步。它允许一个或多个线程等待一组事件发生,直到所有事件都完成后,这些线程才会继续执行。CountDownLatch的核心思想是计数器,当计数器减到0时,等待的线程才会被释放。
🎉 线程安全问题
在多线程环境中,线程安全问题是一个至关重要的议题。CountDownLatch本身是线程安全的,但是使用CountDownLatch时,我们必须确保其操作是线程安全的。
以下是一些使用CountDownLatch时需要注意的线程安全问题:
- 初始化计数器:CountDownLatch的构造函数接受一个整数参数,表示计数器的初始值。这个值必须是大于0的整数。如果初始化为0,那么CountDownLatch将无法正常工作。
CountDownLatch latch = new CountDownLatch(0);
- 计数器减1:每次调用
countDown()
方法时,计数器减1。如果计数器减到0,那么所有等待的线程将被唤醒。
latch.countDown();
- 等待事件发生:调用
await()
方法使当前线程等待,直到计数器减到0。如果计数器大于0,当前线程将被阻塞。
latch.await();
🎉 并发控制
CountDownLatch可以用于实现并发控制,以下是一些示例:
- 线程池执行任务:假设有一个线程池,需要等待所有任务执行完毕后,再进行后续操作。
ExecutorService executor = Executors.newFixedThreadPool(5);
CountDownLatch latch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
// 执行任务
latch.countDown();
});
}
latch.await();
executor.shutdown();
- 多线程访问共享资源:假设有一个共享资源,需要等待所有线程访问完毕后,再进行后续操作。
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
// 访问共享资源
latch.countDown();
}).start();
}
latch.await();
🎉 同步机制
CountDownLatch通过同步机制实现线程间的等待和通知。以下是一些同步机制的要点:
-
共享资源:CountDownLatch的计数器是共享资源,所有线程都可以对其进行操作。
-
锁机制:CountDownLatch内部使用锁机制保证计数器的线程安全。
-
原子操作:
countDown()
和await()
方法都是原子操作,不会受到其他线程的干扰。 -
volatile关键字:CountDownLatch的计数器是volatile类型的,确保其可见性。
🎉 线程状态
CountDownLatch可以影响线程状态,以下是一些线程状态的要点:
-
等待状态:调用
await()
方法后,当前线程进入等待状态,直到计数器减到0。 -
运行状态:当计数器减到0时,等待的线程被唤醒,进入运行状态。
-
阻塞状态:在
await()
方法中,如果计数器大于0,当前线程进入阻塞状态。
🎉 线程通信
CountDownLatch可以实现线程间的通信,以下是一些通信机制的要点:
-
等待通知:线程通过调用
await()
方法等待事件发生,当事件发生时,其他线程通过调用countDown()
方法通知等待的线程。 -
屏障:CountDownLatch可以作为屏障,使线程在事件发生前暂停执行。
🎉 死锁
CountDownLatch本身不会导致死锁,但是使用CountDownLatch时,需要注意避免死锁。
-
避免循环等待:确保所有线程在调用
await()
方法前,都调用了countDown()
方法。 -
合理设置计数器:计数器的值应与线程数量相匹配,避免线程无法唤醒。
🎉 竞态条件
CountDownLatch可以避免竞态条件,以下是一些避免竞态条件的要点:
-
同步操作:使用CountDownLatch同步操作,确保线程安全。
-
原子操作:使用原子操作保证操作的原子性。
🎉 并发编程最佳实践
-
合理设置计数器:根据实际情况设置计数器的值。
-
避免循环等待:确保所有线程在调用
await()
方法前,都调用了countDown()
方法。 -
使用其他同步机制:根据实际情况选择合适的同步机制。
🎉 多线程编程模式
CountDownLatch可以应用于多种多线程编程模式,例如:
-
生产者-消费者模式:用于同步生产者和消费者之间的操作。
-
线程池模式:用于同步线程池中的任务执行。
🎉 并发工具类
CountDownLatch是Java并发工具类之一,其他常用工具类还包括:
-
Semaphore:信号量,用于控制对共享资源的访问。
-
CyclicBarrier:循环屏障,用于线程间的同步。
-
Exchanger:交换器,用于线程间的数据交换。
🎉 线程池
CountDownLatch可以与线程池结合使用,实现线程池中的任务同步。
ExecutorService executor = Executors.newFixedThreadPool(5);
CountDownLatch latch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
// 执行任务
latch.countDown();
});
}
latch.await();
executor.shutdown();
🎉 线程安全集合
CountDownLatch可以与线程安全集合结合使用,例如ConcurrentHashMap
。
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
CountDownLatch latch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
// 添加元素
map.put("key" + i, "value" + i);
latch.countDown();
}).start();
}
latch.await();
🎉 并发编程框架
CountDownLatch可以与并发编程框架结合使用,例如Spring框架。
@Service
public class MyService {
private CountDownLatch latch = new CountDownLatch(5);
public void executeTask() {
latch.await();
// 执行任务
}
}
🎉 性能测试与调优
CountDownLatch的性能取决于线程数量和任务复杂度。以下是一些性能测试与调优的要点:
-
合理设置线程数量:根据实际情况设置线程数量。
-
优化任务执行:优化任务执行,提高效率。
-
监控性能:监控性能,及时发现并解决问题。
线程同步辅助类 | CountDownLatch |
---|---|
核心思想 | 计数器,当计数器减到0时,等待的线程才会被释放 |
线程安全问题 | - 初始化计数器:必须是大于0的整数<br>- 计数器减1:每次调用countDown() 方法,计数器减1<br>- 等待事件发生:调用await() 方法使当前线程等待,直到计数器减到0 |
并发控制 | - 线程池执行任务:等待所有任务执行完毕后,再进行后续操作<br>- 多线程访问共享资源:等待所有线程访问完毕后,再进行后续操作 |
同步机制 | - 共享资源:CountDownLatch的计数器是共享资源<br>- 锁机制:内部使用锁机制保证计数器的线程安全<br>- 原子操作:countDown() 和await() 方法都是原子操作<br>- volatile 关键字:计数器是volatile 类型的,确保其可见性 |
线程状态 | - 等待状态:调用await() 方法后,当前线程进入等待状态<br>- 运行状态:当计数器减到0时,等待的线程被唤醒,进入运行状态<br>- 阻塞状态:在await() 方法中,如果计数器大于0,当前线程进入阻塞状态 |
线程通信 | - 等待通知:线程通过调用await() 方法等待事件发生,当事件发生时,其他线程通过调用countDown() 方法通知等待的线程<br>- 屏障:CountDownLatch可以作为屏障,使线程在事件发生前暂停执行 |
死锁 | - 避免循环等待:确保所有线程在调用await() 方法前,都调用了countDown() 方法<br>- 合理设置计数器:计数器的值应与线程数量相匹配,避免线程无法唤醒 |
竞态条件 | - 同步操作:使用CountDownLatch同步操作,确保线程安全<br>- 原子操作:使用原子操作保证操作的原子性 |
并发编程最佳实践 | - 合理设置计数器:根据实际情况设置计数器的值<br>- 避免循环等待:确保所有线程在调用await() 方法前,都调用了countDown() 方法<br>- 使用其他同步机制:根据实际情况选择合适的同步机制 |
多线程编程模式 | - 生产者-消费者模式:用于同步生产者和消费者之间的操作<br>- 线程池模式:用于同步线程池中的任务执行 |
并发工具类 | - Semaphore:信号量,用于控制对共享资源的访问<br>- CyclicBarrier:循环屏障,用于线程间的同步<br>- Exchanger:交换器,用于线程间的数据交换 |
线程池 | - 与线程池结合使用,实现线程池中的任务同步 |
线程安全集合 | - 与线程安全集合结合使用,例如ConcurrentHashMap |
并发编程框架 | - 与并发编程框架结合使用,例如Spring框架 |
性能测试与调优 | - 合理设置线程数量:根据实际情况设置线程数量<br>- 优化任务执行:优化任务执行,提高效率<br>- 监控性能:监控性能,及时发现并解决问题 |
CountDownLatch在并发编程中扮演着重要的角色,它通过计数器实现线程间的同步。在实际应用中,CountDownLatch可以有效地解决线程间的协作问题,如线程池执行任务、多线程访问共享资源等。例如,在执行一个复杂任务时,我们可以使用CountDownLatch来确保所有线程都完成了自己的子任务,然后再继续执行后续操作。此外,CountDownLatch还可以作为屏障,使线程在事件发生前暂停执行,从而实现线程间的同步。在避免死锁和竞态条件方面,CountDownLatch也提供了有效的解决方案。通过合理设置计数器和避免循环等待,我们可以确保线程安全,提高程序的稳定性。
CountDownLatch是一种同步辅助类,在Java并发编程中,它允许一个或多个线程等待一组事件发生。CountDownLatch的计数器初始值设定为N,每当一个事件发生时,计数器减1,当计数器值变为0时,表示所有事件都已完成,等待的线程可以继续执行。
🎉 适用场景
-
分步任务同步:当多个线程需要按照一定的顺序执行任务时,可以使用CountDownLatch来同步。例如,在分布式系统中,多个节点需要按照顺序执行任务,可以使用CountDownLatch来确保每个节点在执行下一个任务前,必须完成当前任务。
-
多线程并行处理:在多线程并行处理数据时,可以使用CountDownLatch来等待所有线程处理完数据。例如,在处理大量数据时,可以将数据分发给多个线程并行处理,使用CountDownLatch来等待所有线程处理完数据。
-
线程池任务执行:在Java线程池中,可以使用CountDownLatch来等待所有任务执行完毕。例如,在执行完一组任务后,需要等待所有任务执行完毕才能进行下一步操作。
-
生产者-消费者模型:在生产者-消费者模型中,可以使用CountDownLatch来同步生产者和消费者。例如,当生产者生产完一定数量的数据后,可以使用CountDownLatch来通知消费者开始消费数据。
🎉 使用方法
public class CountDownLatchExample {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3);
new Thread(() -> {
System.out.println("Thread 1 is running");
latch.countDown();
}).start();
new Thread(() -> {
System.out.println("Thread 2 is running");
latch.countDown();
}).start();
new Thread(() -> {
System.out.println("Thread 3 is running");
latch.countDown();
}).start();
try {
latch.await();
System.out.println("All threads have finished execution");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在上面的示例中,我们创建了3个线程,每个线程在执行完毕后调用countDown()
方法,当所有线程都执行完毕后,主线程将继续执行。
🎉 与CyclicBarrier对比
CountDownLatch和CyclicBarrier都是用于线程同步的工具类,但它们的使用场景有所不同。
- CountDownLatch:适用于等待一组事件发生,当事件发生完毕后,等待的线程可以继续执行。CountDownLatch只能使用一次。
- CyclicBarrier:适用于线程之间需要定期同步的场景,当所有线程到达某个点后,可以执行一个操作,然后所有线程重新开始执行。CyclicBarrier可以重复使用。
🎉 与Semaphore对比
CountDownLatch和Semaphore都是用于线程同步的工具类,但它们的使用场景有所不同。
- CountDownLatch:适用于等待一组事件发生,当事件发生完毕后,等待的线程可以继续执行。CountDownLatch只能使用一次。
- Semaphore:适用于控制对共享资源的访问,例如,限制同时访问某个资源的线程数量。Semaphore可以重复使用。
🎉 与FutureTask结合使用
FutureTask可以与CountDownLatch结合使用,实现异步任务执行和同步等待任务完成。
public class FutureTaskExample {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(1);
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
System.out.println("FutureTask is running");
return 1;
});
new Thread(futureTask).start();
try {
System.out.println("Waiting for FutureTask to complete");
Integer result = futureTask.get();
System.out.println("FutureTask completed with result: " + result);
latch.countDown();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
try {
System.out.println("Waiting for latch to count down");
latch.await();
System.out.println("Latch counted down");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在上面的示例中,FutureTask异步执行任务,主线程等待FutureTask执行完毕,然后等待CountDownLatch计数器减为0。
🎉 在高并发编程中的应用
CountDownLatch在高并发编程中有着广泛的应用,例如:
- 分布式系统:在分布式系统中,可以使用CountDownLatch来同步多个节点执行任务。
- 多线程并行处理:在多线程并行处理数据时,可以使用CountDownLatch来等待所有线程处理完数据。
- 线程池任务执行:在Java线程池中,可以使用CountDownLatch来等待所有任务执行完毕。
🎉 性能分析
CountDownLatch的性能取决于线程数量和事件数量。当线程数量较多或事件数量较多时,CountDownLatch的性能可能会受到影响。
🎉 最佳实践
- 合理设置计数器初始值:根据实际需求设置计数器初始值,避免过多或过少的线程等待。
- 避免死锁:在使用CountDownLatch时,确保所有线程都能执行
countDown()
方法,避免死锁。 - 合理使用线程池:在多线程并行处理数据时,可以使用线程池来提高性能。
对比项 | CountDownLatch | CyclicBarrier | Semaphore | FutureTask |
---|---|---|---|---|
适用场景 | 等待一组事件发生 | 线程之间需要定期同步 | 控制对共享资源的访问 | 异步任务执行和同步等待任务完成 |
使用次数 | 只能使用一次 | 可以重复使用 | 可以重复使用 | 可以重复使用 |
同步机制 | 计数器减至0 | 线程到达屏障点 | 控制访问权限 | 线程执行任务并返回结果 |
性能影响 | 线程数量和事件数量 | 线程数量和同步次数 | 线程数量和资源数量 | 线程数量和任务复杂度 |
示例代码 | 等待所有线程完成 | 线程到达同步点后执行 | 控制线程访问资源 | 异步执行任务并获取结果 |
应用场景 | 分布式系统同步、多线程并行处理、线程池任务执行 | 线程同步、多线程任务执行 | 资源访问控制、线程池任务执行 | 异步任务执行、线程池任务执行 |
最佳实践 | 合理设置计数器初始值、避免死锁、合理使用线程池 | 确保所有线程到达屏障点、避免死锁、合理使用线程池 | 合理设置信号量数量、避免死锁、合理使用线程池 | 合理设置FutureTask、避免死锁、合理使用线程池 |
在实际应用中,CountDownLatch常用于确保所有线程完成某项任务后再继续执行,例如在分布式系统中同步不同节点间的操作。CyclicBarrier则适用于需要定期同步的线程,如在一个循环中,每个线程都需要在执行完自己的任务后等待其他线程到达同步点。Semaphore则用于控制对共享资源的访问,确保在任何时刻只有一个线程能够访问该资源,这在多线程环境中防止资源竞争非常重要。而FutureTask则常用于异步任务执行,它允许主线程在执行其他任务的同时,等待异步任务的结果。合理选择和使用这些同步工具,可以有效提高程序的性能和稳定性。
🍊 Java高并发知识点之CountDownLatch:示例代码
在大型分布式系统中,高并发处理是保证系统性能和响应速度的关键。Java作为主流的编程语言之一,提供了丰富的并发工具和机制。CountDownLatch是Java并发编程中的一个重要工具,它允许一个或多个线程等待一组事件完成。以下将结合一个实际场景,介绍CountDownLatch的使用及其重要性。
假设我们正在开发一个分布式文件系统,该系统需要将一个大型文件分割成多个小文件,并分布到不同的服务器上进行并行处理。在这个过程中,我们需要确保所有的小文件处理任务都完成后再进行下一步操作,比如合并处理结果。这时,CountDownLatch就能发挥其作用。
CountDownLatch允许我们在启动所有并发任务之前设置一个计数器,每个任务完成时都会减少计数器的值。当计数器值降为0时,所有等待的任务才会继续执行。这种机制确保了所有任务都按照既定的顺序完成,避免了因某些任务未完成而导致的错误。
介绍CountDownLatch的重要性在于,它能够帮助我们简化并发编程的复杂性,确保并发任务按照预期顺序执行。这对于保证系统稳定性和提高效率至关重要。
接下来,我们将通过两个示例来展示CountDownLatch的使用:简单示例和复杂示例。简单示例将展示如何使用CountDownLatch启动多个线程,并等待所有线程完成;复杂示例将展示如何在一个更复杂的场景中使用CountDownLatch,比如在分布式文件系统中同步多个任务。
在简单示例中,我们将创建一个CountDownLatch实例,并在主线程中启动多个子线程。每个子线程在执行完毕后都会调用CountDownLatch的countDown方法,主线程则通过await方法等待所有子线程完成。
在复杂示例中,我们将模拟分布式文件系统的场景,使用CountDownLatch来同步多个任务。例如,我们可以创建一个主任务和多个子任务,主任务负责启动所有子任务,并等待所有子任务完成后才进行合并处理。
通过这两个示例,读者可以更深入地理解CountDownLatch的使用方法和场景,从而在实际项目中更好地应用这一并发工具。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
// CountDownLatch的原理
public void explainCountDownLatch() {
// CountDownLatch是一个同步辅助类,允许一个或多个线程等待其他线程完成操作。
// 它内部维护一个计数器,初始值为给定值,每当一个线程完成操作时,计数器减1。
// 当计数器减到0时,所有等待的线程将被释放。
}
// CountDownLatch的使用场景
public void usageScenarios() {
// 1. 并行任务完成通知:多个线程并行执行任务,主线程等待所有任务完成。
// 2. 分段执行:将一个大任务分解为多个小任务,每个小任务由一个线程执行。
}
// 示例代码
public void exampleCode() {
// 创建一个CountDownLatch实例,初始值为3
CountDownLatch latch = new CountDownLatch(3);
// 创建并启动三个线程
new Thread(() -> {
// 执行任务
System.out.println("Thread 1 is working...");
latch.countDown();
}).start();
new Thread(() -> {
// 执行任务
System.out.println("Thread 2 is working...");
latch.countDown();
}).start();
new Thread(() -> {
// 执行任务
System.out.println("Thread 3 is working...");
latch.countDown();
}).start();
// 主线程等待所有任务完成
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("All tasks are completed.");
}
// 与CyclicBarrier对比
public void compareWithCyclicBarrier() {
// CountDownLatch和CyclicBarrier都是同步辅助类,但它们有区别:
// 1. CountDownLatch只能使用一次,而CyclicBarrier可以重复使用。
// 2. CountDownLatch用于等待多个线程完成,而CyclicBarrier用于等待所有线程到达某个点。
}
// 与其他并发工具类结合使用
public void combineWithOtherConcurrencyTools() {
// CountDownLatch可以与其他并发工具类结合使用,例如Semaphore、ReentrantLock等。
// 例如,使用Semaphore控制线程访问共享资源,使用CountDownLatch等待所有线程完成。
}
// 性能分析
public void performanceAnalysis() {
// CountDownLatch的性能取决于线程数量和任务复杂度。
// 在高并发场景下,CountDownLatch的性能较好,因为它不需要维护线程间的同步状态。
}
// 最佳实践
public void bestPractices() {
// 1. 选择合适的初始值,避免过多等待。
// 2. 使用try-catch块捕获InterruptedException,避免程序异常终止。
// 3. 在实际应用中,CountDownLatch可以与其他并发工具类结合使用,提高程序性能。
}
public static void main(String[] args) {
CountDownLatchExample example = new CountDownLatchExample();
example.explainCountDownLatch();
example.usageScenarios();
example.exampleCode();
example.compareWithCyclicBarrier();
example.combineWithOtherConcurrencyTools();
example.performanceAnalysis();
example.bestPractices();
}
}
以上代码展示了CountDownLatch的原理、使用场景、示例代码、与CyclicBarrier对比、与其他并发工具类结合使用、性能分析和最佳实践。
特性/方面 | CountDownLatch | CyclicBarrier |
---|---|---|
原理 | 允许一个或多个线程等待其他线程完成操作,内部维护一个计数器,初始值为给定值,每当一个线程完成操作时,计数器减1,当计数器减到0时,所有等待的线程将被释放。 | 允许多个线程等待其他线程到达某个点,所有线程到达后,这些线程会执行一个共同的动作,然后所有线程继续执行。 |
使用场景 | 1. 并行任务完成通知:多个线程并行执行任务,主线程等待所有任务完成。2. 分段执行:将一个大任务分解为多个小任务,每个小任务由一个线程执行。 | 1. 并行任务同步:多个线程需要同步到达某个点,然后一起执行某个操作。2. 竞赛:多个线程需要同步开始执行,然后比较结果。 |
重复使用 | 不能重复使用,一旦计数器减到0,CountDownLatch就不能再使用。 | 可以重复使用,通过调用reset方法重置计数器。 |
线程到达点 | 不需要所有线程到达某个点,只需要等待所有线程完成操作。 | 需要所有线程到达某个点,然后一起执行某个操作。 |
结合其他并发工具 | 可以与其他并发工具类结合使用,例如Semaphore、ReentrantLock等。 | 也可以与其他并发工具类结合使用,例如Semaphore、ReentrantLock等。 |
性能 | 在高并发场景下,CountDownLatch的性能较好,因为它不需要维护线程间的同步状态。 | CyclicBarrier的性能取决于线程数量和任务复杂度,通常在高并发场景下性能较好。 |
最佳实践 | 1. 选择合适的初始值,避免过多等待。2. 使用try-catch块捕获InterruptedException,避免程序异常终止。3. 在实际应用中,CountDownLatch可以与其他并发工具类结合使用,提高程序性能。 | 1. 确保所有线程都调用await方法,否则CyclicBarrier不会工作。2. 使用reset方法重置CyclicBarrier,以便重复使用。3. 在实际应用中,CyclicBarrier可以与其他并发工具类结合使用,提高程序性能。 |
CountDownLatch和CyclicBarrier都是Java并发编程中常用的同步工具,它们在实现线程间的协作和同步方面发挥着重要作用。CountDownLatch主要用于等待多个线程完成某个操作,而CyclicBarrier则用于等待所有线程到达某个点,然后一起执行某个操作。在实际应用中,选择合适的同步工具可以显著提高程序的性能和可维护性。例如,在并行任务完成通知的场景中,CountDownLatch可以有效地通知主线程所有子线程已完成任务;而在并行任务同步的场景中,CyclicBarrier则可以确保所有线程同步到达某个点,然后一起执行后续操作。此外,这两种工具还可以与其他并发工具类结合使用,如Semaphore和ReentrantLock,以实现更复杂的并发控制逻辑。
CountDownLatch 是 Java 并发编程中一个非常有用的工具,它允许一个或多个线程等待一组事件发生。下面,我们将深入探讨 CountDownLatch 的使用场景、与 CyclicBarrier 的区别、与其他并发工具类的比较、复杂场景下的应用示例、实现原理、使用注意事项、性能分析以及代码示例。
🎉 CountDownLatch 使用场景
CountDownLatch 主要用于同步多个线程,确保某个线程在所有线程完成某项任务后再继续执行。以下是一些常见的使用场景:
- 分步任务执行:在多个线程中执行分步任务,确保所有线程完成第一步后再开始第二步。
- 线程等待:线程 A 需要等待线程 B、C 和 D 完成任务后才能继续执行。
- 多线程计算:在多线程计算中,确保所有线程完成计算后再汇总结果。
🎉 CountDownLatch 与 CyclicBarrier 的区别
CountDownLatch 和 CyclicBarrier 都可以用于线程同步,但它们有一些区别:
- 计数器:CountDownLatch 的计数器只能减到 0,而 CyclicBarrier 的计数器可以循环使用。
- 等待方式:CountDownLatch 的线程在计数器减到 0 之前会一直等待,而 CyclicBarrier 的线程在计数器减到 0 之前会继续执行,直到所有线程都到达屏障点。
- 使用场景:CountDownLatch 适用于线程数量不确定的场景,而 CyclicBarrier 适用于线程数量确定且需要循环等待的场景。
🎉 CountDownLatch 与其他并发工具类的比较
CountDownLatch 与其他并发工具类(如 Semaphore、ReentrantLock)相比,具有以下特点:
- 简单易用:CountDownLatch 的使用非常简单,只需初始化一个计数器,并在任务完成后调用 countDown() 方法。
- 无锁设计:CountDownLatch 采用无锁设计,性能较高。
- 功能单一:CountDownLatch 的功能相对单一,主要用于线程同步。
🎉 CountDownLatch 在复杂场景下的应用示例
以下是一个复杂场景下的 CountDownLatch 应用示例:
public class ComplexCountDownLatchExample {
private final CountDownLatch latch = new CountDownLatch(3);
public void task1() {
// 执行任务 1
System.out.println("Task 1 completed.");
latch.countDown();
}
public void task2() {
// 执行任务 2
System.out.println("Task 2 completed.");
latch.countDown();
}
public void task3() {
// 执行任务 3
System.out.println("Task 3 completed.");
latch.countDown();
}
public void waitForTasks() {
try {
latch.await();
System.out.println("All tasks completed.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ComplexCountDownLatchExample example = new ComplexCountDownLatchExample();
example.task1();
example.task2();
example.task3();
example.waitForTasks();
}
}
🎉 CountDownLatch 的实现原理
CountDownLatch 的实现原理主要基于 AQS(AbstractQueuedSynchronizer)同步器。当线程调用 await() 方法时,会将当前线程放入等待队列,直到计数器减到 0,线程才会从等待队列中唤醒。
🎉 CountDownLatch 的使用注意事项
- 避免死锁:在使用 CountDownLatch 时,确保所有线程都调用了 countDown() 方法,否则可能导致死锁。
- 避免重复调用:不要重复调用 countDown() 方法,否则可能导致计数器减到负数。
- 线程安全:CountDownLatch 是线程安全的,但使用时要注意线程安全问题。
🎉 CountDownLatch 的性能分析
CountDownLatch 的性能较高,因为它采用无锁设计。在多线程环境下,CountDownLatch 的性能优于其他同步工具类。
🎉 CountDownLatch 的代码示例
以下是一个简单的 CountDownLatch 代码示例:
public class CountDownLatchExample {
private final CountDownLatch latch = new CountDownLatch(3);
public void task1() {
// 执行任务 1
System.out.println("Task 1 completed.");
latch.countDown();
}
public void task2() {
// 执行任务 2
System.out.println("Task 2 completed.");
latch.countDown();
}
public void task3() {
// 执行任务 3
System.out.println("Task 3 completed.");
latch.countDown();
}
public void waitForTasks() {
try {
latch.await();
System.out.println("All tasks completed.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
CountDownLatchExample example = new CountDownLatchExample();
example.task1();
example.task2();
example.task3();
example.waitForTasks();
}
}
特征/比较项 | CountDownLatch | CyclicBarrier | Semaphore | ReentrantLock |
---|---|---|---|---|
计数器特性 | 计数器只能减到 0,不可循环使用 | 计数器可以循环使用 | 无计数器,用于控制访问量 | 无计数器,用于控制锁的获取 |
等待方式 | 线程在计数器减到 0 之前会一直等待 | 线程在计数器减到 0 之前会继续执行,直到所有线程都到达屏障点 | 线程在获取信号量之前会一直等待 | 线程在获取锁之前会一直等待 |
适用场景 | 线程数量不确定的场景,需要同步多个线程 | 线程数量确定且需要循环等待的场景 | 控制对共享资源的访问量 | 需要更复杂的锁控制,如可重入锁 |
简单易用 | 简单易用,只需初始化计数器,并在任务完成后调用 countDown() 方法 | 简单易用,只需初始化屏障点,并在任务完成后调用 await() 方法 | 简单易用,只需初始化信号量,并在任务完成后释放信号量 | 简单易用,只需初始化锁,并在任务完成后释放锁 |
无锁设计 | 采用无锁设计,性能较高 | 采用无锁设计,性能较高 | 采用无锁设计,性能较高 | 采用锁设计,性能取决于锁的实现 |
功能单一 | 功能单一,主要用于线程同步 | 功能单一,主要用于线程同步 | 功能单一,主要用于控制访问量 | 功能单一,主要用于锁控制 |
性能 | 性能较高,因为采用无锁设计 | 性能较高,因为采用无锁设计 | 性能取决于信号量的实现 | 性能取决于锁的实现 |
CountDownLatch和CyclicBarrier在实现线程同步时各有千秋。CountDownLatch适用于线程数量不确定的场景,而CyclicBarrier则适用于线程数量确定且需要循环等待的场景。此外,CountDownLatch的计数器只能减到0,不可循环使用,而CyclicBarrier的计数器可以循环使用,这使得CyclicBarrier在需要多次同步的场景中更为灵活。在实际应用中,应根据具体需求选择合适的同步工具。
🍊 Java高并发知识点之CountDownLatch:与其他同步工具的比较
在当今的软件开发领域,高并发编程已成为一项至关重要的技能。特别是在处理大规模数据和高负载应用时,如何有效地实现线程同步和协调成为了一个关键问题。CountDownLatch作为一种同步工具,在Java并发编程中扮演着重要角色。本文将深入探讨CountDownLatch与其他同步工具的比较,以帮助读者更好地理解和应用这一知识点。
在现实场景中,假设我们正在开发一个分布式系统,该系统需要多个线程共同完成一个复杂的任务。例如,一个在线视频平台需要处理大量的视频上传请求,每个请求都需要经过多个处理步骤,如视频压缩、元数据提取等。在这个过程中,我们需要确保所有步骤都按顺序执行,且每个步骤完成后才能开始下一个步骤。这时,CountDownLatch就能发挥其作用,它允许一个或多个线程等待其他线程完成某个操作。
CountDownLatch之所以重要,是因为它提供了一种简单而有效的线程同步机制。与其他同步工具相比,CountDownLatch具有以下特点:
- CountDownLatch允许一个或多个线程等待其他线程完成某个操作,而Semaphore则允许一定数量的线程同时访问某个资源。
- CountDownLatch只能使用一次,而CyclicBarrier可以重复使用,因为它允许线程在完成一次操作后重新开始新一轮的同步。
- CountDownLatch主要用于等待多个线程完成某个操作,而Semaphore则更适用于控制对共享资源的访问。
接下来,我们将分别比较CountDownLatch与CyclicBarrier以及Semaphore。首先,我们将探讨CountDownLatch与CyclicBarrier的区别。CyclicBarrier允许一组线程在到达某个屏障点时等待,直到所有线程都到达屏障点后,这些线程才会继续执行。与CountDownLatch相比,CyclicBarrier更适合于需要重复执行的任务,因为它允许线程在完成一次操作后重新开始新一轮的同步。
其次,我们将比较CountDownLatch与Semaphore。Semaphore是一种信号量,它可以控制对共享资源的访问。与CountDownLatch相比,Semaphore更适合于控制对有限资源的访问,如数据库连接、文件锁等。
通过本文的介绍,读者可以了解到CountDownLatch与其他同步工具的比较,从而更好地选择合适的同步机制来解决实际问题。在后续内容中,我们将进一步探讨CountDownLatch与CyclicBarrier以及Semaphore的具体应用场景和实现方法。
// CountDownLatch 示例代码
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
// 创建一个CountDownLatch,初始计数为3
CountDownLatch latch = new CountDownLatch(3);
// 创建并启动三个线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
// 执行任务
System.out.println(Thread.currentThread().getName() + " 开始执行任务");
// 执行完毕后,计数减1
latch.countDown();
}).start();
}
// 主线程等待所有任务完成
System.out.println(Thread.currentThread().getName() + " 等待其他线程完成");
latch.await();
System.out.println(Thread.currentThread().getName() + " 所有任务完成");
}
}
CountDownLatch和CyclicBarrier都是Java并发编程中常用的线程同步工具,它们在并发编程实践中扮演着重要的角色。下面将详细阐述这两个工具的应用场景、工作原理、区别与联系,以及性能比较和最佳实践。
应用场景
CountDownLatch适用于场景,其中一组线程需要等待一组线程完成某个操作后才能继续执行。例如,在并行计算中,主线程需要等待所有计算线程完成计算后才能进行结果汇总。
CyclicBarrier适用于场景,其中一组线程需要在某个操作完成后,所有线程都到达一个同步点,然后一起执行某个操作。例如,在分布式系统中,多个节点需要在数据同步后一起进行下一步操作。
工作原理
CountDownLatch内部维护一个计数器,初始值为线程数量。每个线程执行完毕后,调用countDown()
方法,计数器减1。当计数器为0时,表示所有线程都已完成,此时调用await()
方法的线程将被唤醒。
CyclicBarrier内部维护一个屏障,每个线程到达屏障后都会调用await()
方法。当所有线程都到达屏障后,屏障打开,所有线程一起执行屏障后的操作。执行完毕后,屏障关闭,可以重复使用。
区别与联系
CountDownLatch是一次性的,计数器减到0后无法重置;而CyclicBarrier可以重复使用,每次操作后屏障都会关闭和打开。
CountDownLatch主要用于线程间的计数同步,而CyclicBarrier主要用于线程间的同步操作。
线程同步机制
CountDownLatch和CyclicBarrier都是基于共享锁的线程同步机制。它们通过共享变量来协调线程间的行为,确保线程按照预期顺序执行。
并发编程实践
在实际应用中,CountDownLatch和CyclicBarrier可以根据具体场景灵活使用。例如,在并行计算中,可以使用CountDownLatch来等待所有计算线程完成;在分布式系统中,可以使用CyclicBarrier来确保所有节点完成数据同步。
性能比较
CountDownLatch和CyclicBarrier的性能取决于具体场景和线程数量。在大多数情况下,它们的性能相差不大。在实际应用中,可以根据具体需求选择合适的工具。
最佳实践
- 在使用CountDownLatch和CyclicBarrier时,确保线程数量与计数器或屏障的初始值一致。
- 避免在CountDownLatch和CyclicBarrier中使用过多的同步锁,以免影响性能。
- 在使用CountDownLatch和CyclicBarrier时,注意线程安全,避免出现竞态条件。
工具名称 | 应用场景 | 工作原理 | 区别与联系 | 线程同步机制 | 并发编程实践 | 性能比较 | 最佳实践 |
---|---|---|---|---|---|---|---|
CountDownLatch | 一组线程需要等待一组线程完成某个操作后才能继续执行,如并行计算中的结果汇总 | 内部维护一个计数器,初始值为线程数量,线程执行完毕后计数器减1,计数器为0时唤醒调用await()方法的线程 | 一次性的,计数器减到0后无法重置;CyclicBarrier可以重复使用 | 基于共享锁的线程同步机制 | 根据具体场景灵活使用,如并行计算中使用CountDownLatch等待计算线程完成 | 大多数情况下性能相差不大 | 确保线程数量与计数器或屏障的初始值一致,避免使用过多的同步锁,注意线程安全 |
CyclicBarrier | 一组线程需要在某个操作完成后,所有线程都到达一个同步点,然后一起执行某个操作,如分布式系统中的数据同步 | 内部维护一个屏障,线程到达屏障后调用await()方法,所有线程到达屏障后执行屏障后的操作,执行完毕后屏障关闭和打开 | 一次性的,计数器减到0后无法重置;CyclicBarrier可以重复使用 | 基于共享锁的线程同步机制 | 根据具体场景灵活使用,如分布式系统中使用CyclicBarrier确保节点完成数据同步 | 大多数情况下性能相差不大 | 确保线程数量与计数器或屏障的初始值一致,避免使用过多的同步锁,注意线程安全 |
CountDownLatch和CyclicBarrier在并行编程中扮演着重要的角色,它们都用于线程同步,但应用场景和工作原理有所不同。CountDownLatch适用于一组线程需要等待其他线程完成某个操作后才能继续执行的场景,而CyclicBarrier则用于线程需要在某个操作完成后,所有线程都到达一个同步点,然后一起执行某个操作。在实际应用中,应根据具体需求选择合适的同步工具,并注意线程安全,避免使用过多的同步锁。
CountDownLatch与Semaphore都是Java并发编程中用于控制线程同步的工具,它们在实现并发控制方面各有特点。以下是关于CountDownLatch与Semaphore的比较,包括使用场景、性能比较、适用场景分析、代码示例、API对比、线程安全和并发编程实践。
🎉 使用场景
CountDownLatch: CountDownLatch主要用于等待一组线程完成某个操作后再继续执行。它适用于场景,如主线程需要等待多个子线程执行完毕后,再进行下一步操作。
Semaphore: Semaphore用于控制对共享资源的访问量。它适用于场景,如限制对某个资源的并发访问数,确保不超过最大并发数。
🎉 性能比较
CountDownLatch: CountDownLatch的性能相对较低,因为它依赖于线程间的通信和等待。当需要等待的线程数量较多时,性能可能会受到影响。
Semaphore: Semaphore的性能相对较高,因为它允许线程在获取到信号量后立即执行,而不需要等待其他线程。
🎉 适用场景分析
CountDownLatch: 适用于以下场景:
- 主线程需要等待多个子线程执行完毕后,再进行下一步操作。
- 需要确保某个操作在所有线程完成后才能执行。
Semaphore: 适用于以下场景:
- 需要限制对共享资源的并发访问数。
- 需要确保某个资源在达到最大并发数之前,不允许其他线程访问。
🎉 代码示例
CountDownLatch:
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int threadCount = 5;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " is running.");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}).start();
}
latch.await();
System.out.println("All threads have finished execution.");
}
}
Semaphore:
public class SemaphoreExample {
public static void main(String[] args) {
int maxConcurrentThreads = 3;
Semaphore semaphore = new Semaphore(maxConcurrentThreads);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " is running.");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}
🎉 API对比
CountDownLatch:
public CountDownLatch(int count)
:构造一个具有给定初始计数的新CountDownLatch。public void await()
:当前线程等待,直到计数达到零。public void countDown()
:递减计数,如果计数达到零,则释放所有等待的线程。
Semaphore:
public Semaphore(int permits)
:构造一个具有给定初始许可数的Semaphore。public void acquire()
:获取一个许可。public void release()
:释放一个许可。
🎉 线程安全
CountDownLatch和Semaphore都是线程安全的,因为它们内部使用了同步机制来保证线程间的正确交互。
🎉 并发编程实践
在实际开发中,CountDownLatch和Semaphore可以根据具体场景灵活运用。例如,在分布式系统中,可以使用Semaphore来控制对某个资源的并发访问;在多线程任务执行中,可以使用CountDownLatch来确保所有线程执行完毕后再进行下一步操作。
特征 | CountDownLatch | Semaphore |
---|---|---|
使用场景 | - 主线程等待多个子线程完成 | - 控制对共享资源的并发访问 |
- 确保操作在所有线程完成后执行 | - 限制并发访问数 | |
性能比较 | - 性能相对较低,依赖于线程通信和等待 | - 性能相对较高,允许线程立即执行 |
适用场景分析 | - 主线程需要等待多个子线程执行完毕 | - 需要限制对共享资源的并发访问 |
- 确保某个操作在所有线程完成后才能执行 | - 需要确保某个资源在达到最大并发数之前,不允许其他线程访问 | |
代码示例 | - CountDownLatchExample.java | - SemaphoreExample.java |
API对比 | - public CountDownLatch(int count) |
- public Semaphore(int permits) |
- public void await() |
- public void acquire() |
|
- public void countDown() |
- public void release() |
|
线程安全 | - 线程安全,内部使用同步机制 | - 线程安全,内部使用同步机制 |
并发编程实践 | - 在多线程任务执行中确保线程同步 | - 在分布式系统中控制资源访问 |
CountDownLatch和Semaphore在并发编程中扮演着不同的角色。CountDownLatch主要用于主线程等待多个子线程完成,确保操作在所有线程完成后执行,适用于需要同步多个线程的场景。而Semaphore则用于控制对共享资源的并发访问,限制并发访问数,适用于需要控制资源访问的场景。在实际应用中,应根据具体需求选择合适的工具,以达到最佳的性能和效果。例如,在分布式系统中,Semaphore可以用来控制对共享资源的访问,从而保证系统的稳定性和可靠性。
🍊 Java高并发知识点之CountDownLatch:总结
在大型分布式系统中,高并发处理是保证系统性能和稳定性的关键。Java作为主流的编程语言之一,在高并发编程领域提供了丰富的工具和类库。CountDownLatch是Java并发编程中的一个重要组件,它能够帮助开发者实现线程间的同步。以下是对Java高并发知识点之CountDownLatch的总结。
在分布式系统中,我们常常会遇到这样一个场景:多个线程需要等待某些操作完成后才能继续执行。例如,在一个分布式任务处理系统中,主线程需要等待所有从线程完成数据处理后,才能进行后续的汇总工作。在这种情况下,CountDownLatch能够发挥重要作用。
CountDownLatch是一个同步辅助类,它允许一个或多个线程等待其他线程完成某个操作。它内部维护了一个计数器,初始值为指定值。每当一个线程完成操作时,它会调用CountDownLatch的countDown()方法,将计数器减一。当计数器减至零时,所有等待的线程将被唤醒,继续执行。
CountDownLatch之所以重要,是因为它能够简化线程间的同步逻辑,提高代码的可读性和可维护性。在Java并发编程中,CountDownLatch常用于以下场景:
-
等待多个线程完成初始化:在分布式系统中,多个线程可能需要等待某些资源或服务初始化完成后才能继续执行。CountDownLatch可以确保所有线程在资源或服务初始化完成后,再继续执行。
-
等待多个线程完成数据处理:在分布式任务处理系统中,主线程需要等待所有从线程完成数据处理后,才能进行后续的汇总工作。CountDownLatch可以确保所有从线程在数据处理完成后,再唤醒主线程进行汇总。
-
等待多个线程完成特定操作:在某些业务场景中,可能需要等待多个线程完成特定操作后,才能继续执行。CountDownLatch可以确保所有线程在操作完成后,再继续执行。
接下来,我们将对CountDownLatch的总结要点进行详细阐述,并对其未来发展趋势进行展望。总结要点将包括CountDownLatch的基本用法、常见场景以及与其他同步组件的比较。展望部分将探讨CountDownLatch在Java并发编程中的潜在应用和改进方向。通过本文的介绍,读者将能够全面了解CountDownLatch在Java高并发编程中的重要性,并掌握其应用技巧。
CountDownLatch是一种同步辅助类,在Java并发编程中用于实现线程间的同步。它允许一个或多个线程等待一组事件发生,直到所有事件都完成后,这些线程才会继续执行。
🎉 工作原理
CountDownLatch内部维护了一个计数器,初始值为指定值。每当一个线程完成一个事件时,它会调用countDown()
方法,将计数器减1。当计数器减到0时,表示所有事件都已完成,此时等待的线程会被唤醒,继续执行。
🎉 应用场景
CountDownLatch适用于以下场景:
- 分步任务执行:在多个线程中执行多个步骤,每个步骤完成后,等待所有步骤完成再继续执行。
- 线程等待:主线程等待多个子线程完成各自的任务后再继续执行。
- 多线程计算:在多个线程中计算结果,等待所有线程计算完成后,再进行汇总。
🎉 与CyclicBarrier对比
CountDownLatch和CyclicBarrier都是线程同步工具,但它们的使用场景有所不同。
- CountDownLatch:适用于等待一组事件完成,但不关心事件执行的顺序。
- CyclicBarrier:适用于等待一组事件完成,且事件执行的顺序很重要。
🎉 与Semaphore对比
CountDownLatch和Semaphore都是线程同步工具,但它们的功能不同。
- CountDownLatch:用于等待一组事件完成。
- Semaphore:用于控制对共享资源的访问,限制同时访问资源的线程数量。
🎉 代码示例
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3);
new Thread(() -> {
System.out.println("Thread 1: Starting...");
latch.countDown();
System.out.println("Thread 1: Completed.");
}).start();
new Thread(() -> {
System.out.println("Thread 2: Starting...");
latch.countDown();
System.out.println("Thread 2: Completed.");
}).start();
new Thread(() -> {
System.out.println("Thread 3: Starting...");
latch.countDown();
System.out.println("Thread 3: Completed.");
}).start();
try {
System.out.println("Main: Waiting for all threads to complete...");
latch.await();
System.out.println("Main: All threads completed.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
🎉 最佳实践
- 避免递归调用:CountDownLatch的
await()
方法可能会被递归调用,这会导致死锁。 - 合理设置计数器值:根据实际需求设置计数器值,避免过多或过少的线程等待。
🎉 性能分析
CountDownLatch的性能取决于线程数量和事件数量。在大量线程和事件的情况下,CountDownLatch的性能可能会受到影响。
🎉 线程安全
CountDownLatch是线程安全的,因为它内部使用了同步机制来保证线程安全。
🎉 并发编程
CountDownLatch是Java并发编程中常用的同步工具,可以帮助开发者实现线程间的同步。在实际开发中,应根据具体需求选择合适的同步工具。
对比项 | CountDownLatch | CyclicBarrier | Semaphore |
---|---|---|---|
工作原理 | 维护一个计数器,线程完成事件后计数器减1,计数器为0时唤醒等待线程 | 维护一个计数器,线程到达屏障后等待,所有线程到达后继续执行 | 维护一个计数器,线程获取许可后计数器减1,计数器为0时允许线程进入,释放许可后计数器加1 |
应用场景 | 等待一组事件完成,不关心事件执行顺序 | 等待一组事件完成,且事件执行顺序很重要 | 控制对共享资源的访问,限制同时访问资源的线程数量 |
代码示例 | CountDownLatch latch = new CountDownLatch(3); |
CyclicBarrier barrier = new CyclicBarrier(3); |
Semaphore semaphore = new Semaphore(1); |
最佳实践 | 避免递归调用await() 方法,合理设置计数器值 |
避免递归调用await() 方法,确保所有线程都到达屏障 |
避免在acquire() 和release() 方法中发生死锁,合理设置许可数量 |
性能分析 | 在大量线程和事件的情况下,性能可能会受到影响 | 在大量线程和事件的情况下,性能可能会受到影响 | 在大量线程和事件的情况下,性能可能会受到影响 |
线程安全 | 线程安全,内部使用同步机制保证线程安全 | 线程安全,内部使用同步机制保证线程安全 | 线程安全,内部使用同步机制保证线程安全 |
并发编程 | Java并发编程中常用的同步工具,实现线程间同步 | Java并发编程中常用的同步工具,实现线程间同步 | Java并发编程中常用的同步工具,控制对共享资源的访问 |
CountDownLatch和CyclicBarrier在实现线程同步方面各有特点。CountDownLatch适用于不需要关心事件执行顺序的场景,而CyclicBarrier则强调事件执行的顺序性。在实际应用中,应根据具体需求选择合适的同步工具。例如,在实现多线程的并行计算时,CountDownLatch可以确保所有线程完成计算后再继续执行后续操作;而在实现多线程的顺序执行时,CyclicBarrier则可以保证线程按照既定的顺序执行。此外,Semaphore在控制对共享资源的访问方面具有重要作用,通过限制同时访问资源的线程数量,可以有效避免资源竞争和死锁问题。
// CountDownLatch 原理
public class CountDownLatchExample {
// CountDownLatch 是一个同步辅助类,用于等待一组事件发生
private final CountDownLatch latch = new CountDownLatch(3);
public void doWork() {
// 模拟工作执行
System.out.println("开始工作...");
try {
// 执行任务,不阻塞
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 完成工作,计数减一
latch.countDown();
System.out.println("工作完成,等待其他工作完成...");
}
public void awaitWork() {
try {
// 等待所有工作完成
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有工作完成,继续执行...");
}
public static void main(String[] args) {
CountDownLatchExample example = new CountDownLatchExample();
// 创建线程执行工作
new Thread(example::doWork).start();
new Thread(example::doWork).start();
new Thread(example::doWork).start();
// 等待所有工作完成
example.awaitWork();
}
}
CountDownLatch 的原理是通过一个计数器来控制线程的执行。当计数器大于0时,当前线程会等待,直到计数器为0时,所有等待的线程才会继续执行。
🎉 使用场景
CountDownLatch 常用于以下场景:
- 等待一组事件发生,例如等待多个线程完成工作。
- 在主线程中等待子线程执行完毕。
- 在多线程环境中,确保某些操作在所有线程完成后再执行。
🎉 与CyclicBarrier区别
CyclicBarrier 和 CountDownLatch 都可以用来同步线程,但它们的使用场景有所不同:
- CountDownLatch 用于等待一组事件发生,而 CyclicBarrier 用于等待所有线程到达某个点后再继续执行。
- CountDownLatch 只能使用一次,而 CyclicBarrier 可以重复使用。
🎉 与Semaphore比较
Semaphore 和 CountDownLatch 都可以用来控制线程的并发执行,但它们的使用方式不同:
- Semaphore 用于控制对共享资源的访问,而 CountDownLatch 用于等待一组事件发生。
- Semaphore 可以设置多个许可,而 CountDownLatch 只有一个计数器。
🎉 并发编程应用
CountDownLatch 在并发编程中有很多应用,例如:
- 在多线程环境中,确保某些操作在所有线程完成后再执行。
- 在分布式系统中,确保所有节点完成某个操作后再继续执行。
🎉 性能分析
CountDownLatch 的性能取决于以下因素:
- 线程数量
- 计数器的值
- 线程的执行时间
🎉 最佳实践
- 在使用 CountDownLatch 时,确保计数器的值正确。
- 在使用 CountDownLatch 时,避免在等待过程中修改计数器的值。
- 在使用 CountDownLatch 时,确保所有线程都调用了 countDown() 方法。
🎉 未来发展趋势
随着云计算和分布式系统的不断发展,CountDownLatch 将在更多场景中得到应用。同时,CountDownLatch 的性能和功能也将得到进一步提升。
特性/概念 | CountDownLatch | CyclicBarrier | Semaphore |
---|---|---|---|
原理 | 通过一个计数器控制线程执行,计数器大于0时线程等待,计数器为0时线程继续执行。 | 等待所有线程到达某个点后再继续执行,可以重复使用。 | 控制对共享资源的访问,可以设置多个许可。 |
使用场景 | 等待一组事件发生,如多个线程完成工作。 | 确保所有线程到达某个点后再继续执行。 | 控制对共享资源的并发访问。 |
区别 | 只能使用一次,用于等待事件发生。 | 可以重复使用,用于到达某个点后再执行。 | 用于控制对共享资源的访问,与事件无关。 |
性能 | 性能取决于线程数量、计数器值和线程执行时间。 | 性能取决于线程数量和执行点。 | 性能取决于许可数量和线程数量。 |
应用 | 确保操作在所有线程完成后再执行,分布式系统中的节点同步。 | 线程同步,到达某个点后再执行操作。 | 控制对共享资源的并发访问。 |
最佳实践 | 确保计数器值正确,避免在等待过程中修改计数器值,所有线程调用 countDown()。 | 确保所有线程到达执行点,正确使用 reset() 方法。 | 确保许可数量正确,避免死锁。 |
未来发展趋势 | 在云计算和分布式系统中得到更多应用,性能和功能将进一步提升。 | 可能增加更多功能,如支持动态调整执行点。 | 可能与其他同步机制结合,提供更丰富的并发控制功能。 |
CountDownLatch和CyclicBarrier在实现线程同步方面各有千秋,CountDownLatch通过计数器控制线程执行,适用于等待一组事件发生,而CyclicBarrier则确保所有线程到达某个点后再继续执行,可以重复使用,适用于需要多次同步的场景。在实际应用中,应根据具体需求选择合适的同步机制,以优化系统性能和资源利用效率。
博主分享
📥博主的人生感悟和目标
📙经过多年在CSDN创作上千篇文章的经验积累,我已经拥有了不错的写作技巧。同时,我还与清华大学出版社签下了四本书籍的合约,并将陆续出版。
- 《Java项目实战—深入理解大型互联网企业通用技术》基础篇的购书链接:https://item.jd.com/14152451.html
- 《Java项目实战—深入理解大型互联网企业通用技术》基础篇繁体字的购书链接:http://product.dangdang.com/11821397208.html
- 《Java项目实战—深入理解大型互联网企业通用技术》进阶篇的购书链接:https://item.jd.com/14616418.html
- 《Java项目实战—深入理解大型互联网企业通用技术》架构篇待上架
- 《解密程序员的思维密码--沟通、演讲、思考的实践》购书链接:https://item.jd.com/15096040.html
面试备战资料
八股文备战
场景 | 描述 | 链接 |
---|---|---|
时间充裕(25万字) | Java知识点大全(高频面试题) | Java知识点大全 |
时间紧急(15万字) | Java高级开发高频面试题 | Java高级开发高频面试题 |
理论知识专题(图文并茂,字数过万)
技术栈 | 链接 |
---|---|
RocketMQ | RocketMQ详解 |
Kafka | Kafka详解 |
RabbitMQ | RabbitMQ详解 |
MongoDB | MongoDB详解 |
ElasticSearch | ElasticSearch详解 |
Zookeeper | Zookeeper详解 |
Redis | Redis详解 |
MySQL | MySQL详解 |
JVM | JVM详解 |
集群部署(图文并茂,字数过万)
技术栈 | 部署架构 | 链接 |
---|---|---|
MySQL | 使用Docker-Compose部署MySQL一主二从半同步复制高可用MHA集群 | Docker-Compose部署教程 |
Redis | 三主三从集群(三种方式部署/18个节点的Redis Cluster模式) | 三种部署方式教程 |
RocketMQ | DLedger高可用集群(9节点) | 部署指南 |
Nacos+Nginx | 集群+负载均衡(9节点) | Docker部署方案 |
Kubernetes | 容器编排安装 | 最全安装教程 |
开源项目分享
项目名称 | 链接地址 |
---|---|
高并发红包雨项目 | https://gitee.com/java_wxid/red-packet-rain |
微服务技术集成demo项目 | https://gitee.com/java_wxid/java_wxid |
管理经验
【公司管理与研发流程优化】针对研发流程、需求管理、沟通协作、文档建设、绩效考核等问题的综合解决方案:https://download.csdn.net/download/java_wxid/91148718
希望各位读者朋友能够多多支持!
现在时代变了,信息爆炸,酒香也怕巷子深,博主真的需要大家的帮助才能在这片海洋中继续发光发热,所以,赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!
- 💂 博客主页: Java程序员廖志伟
- 👉 开源项目:Java程序员廖志伟
- 🌥 哔哩哔哩:Java程序员廖志伟
- 🎏 个人社区:Java程序员廖志伟
- 🔖 个人微信号:
SeniorRD
🔔如果您需要转载或者搬运这篇文章的话,非常欢迎您私信我哦~
更多推荐
所有评论(0)