活动介绍

Java并发编程:线程管理与协调

立即解锁
发布时间: 2025-08-19 02:34:03 阅读量: 5 订阅数: 34
PDF

JVM并发编程的艺术与实践

# Java并发编程:线程管理与协调 ## 1. 使用 ExecutorService 管理线程 在计算目录大小时,我们可以将操作拆分为多个任务,每个任务负责探索一个子目录。但直接使用`Thread`实例存在问题,因为线程不可复用,且调度多个任务并不容易,创建过多线程也不可扩展。`java.util.concurrent` API 正是为管理线程池而引入的。 ### 1.1 ExecutorService 概述 - `ExecutorService`代表一个线程池,它将线程的创建和执行的任务分离。 - 可以配置线程池的类型,如单线程、缓存、基于优先级、定时/周期性或固定大小的线程池,还能设置任务等待队列的大小。 - 可轻松调度任意数量的任务,对于无返回结果的任务,可将其包装在`Runnable`中;对于有返回结果的任务,可将其包装在`Callable`中。 ### 1.2 任务调度方法 | 方法 | 描述 | | ---- | ---- | | `execute()` | 用于调度任意任务 | | `submit()` | 用于调度任意任务,可返回`Future`对象获取任务结果 | | `invokeAll()` | 用于调度一组任务 | | `invokeAny()` | 用于只关心其中一个任务完成的情况 | 这些方法都有带超时参数的重载版本,用于设置等待结果的时间。 ### 1.3 线程池的生命周期管理 - 创建`ExecutorService`后,线程池即准备好并处于活动状态。若没有任务,线程会空闲,缓存池中的线程会在延迟后死亡。 - 不再需要线程池时,调用`shutdown()`方法,它会在完成所有已调度任务后关闭线程池,但不能再调度新任务。 - 调用`shutdownNow()`方法会尝试强制取消当前执行的任务,但不保证成功,因为它依赖任务对`interrupt()`调用的响应。 ### 1.4 示例代码 ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadManagementExample { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(10); // 调度任务 service.submit(() -> System.out.println("Task executed")); // 关闭线程池 service.shutdown(); } } ``` ### 1.5 避免依赖线程终止检查 `ExecutorService`提供了检查服务是否终止或关闭的方法,但最好不要依赖它们。我们应关注任务的完成,而非线程或服务的死亡。 ## 2. 线程协调 将问题拆分为多个部分后,我们可以在线程池中调度多个并发任务,并等待它们的结果。 ### 2.1 顺序计算文件大小示例 ```java import java.io.File; public class TotalFileSizeSequential { private long getTotalSizeOfFilesInDir(final File file) { if (file.isFile()) return file.length(); final File[] children = file.listFiles(); long total = 0; if (children != null) for (final File child : children) total += getTotalSizeOfFilesInDir(child); return total; } public static void main(final String[] args) { final long start = System.nanoTime(); final long total = new TotalFileSizeSequential() .getTotalSizeOfFilesInDir(new File(args[0])); final long end = System.nanoTime(); System.out.println("Total Size: " + total); System.out.println("Time taken: " + (end - start) / 1.0e9); } } ``` ### 2.2 简单并发计算文件大小示例 ```java import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public class NaivelyConcurrentTotalFileSize { private long getTotalSizeOfFilesInDir( final ExecutorService service, final File file) throws InterruptedException, ExecutionException, TimeoutException { if (file.isFile()) return file.length(); long total = 0; final File[] children = file.listFiles(); if (children != null) { final List<Future<Long>> partialTotalFutures = new ArrayList<Future<Long>>(); for (final File child : children) { partialTotalFutures.add(service.submit(new Callable<Long>() { public Long call() throws InterruptedException, ExecutionException, TimeoutException { return getTotalSizeOfFilesInDir(service, child); } })); } for (final Future<Long> partialTotalFuture : partialTotalFutures) total += partialTotalFuture.get(100, TimeUnit.SECONDS); } return total; } private long getTotalSizeOfFile(final String fileName) throws InterruptedException, ExecutionException, TimeoutException { final ExecutorService service = Executors.newFixedThreadPool(100); try { return getTotalSizeOfFilesInDir(service, new File(fileName)); } finally { service.shutdown(); } } public static void main(final String[] args) throws InterruptedException, ExecutionException, TimeoutException { final long start = System.nanoTime(); final long total = new NaivelyConcurrentTotalFileSize() .getTotalSizeOfFile(args[0]); final long end = System.nanoTime(); System.out.println("Total Size: " + total); System.out.println("Time taken: " + (end - start) / 1.0e9); } } ``` ### 2.3 简单并发版本的问题 简单并发版本存在问题,如在计算`/etc`目录大小时,速度比顺序版本还慢,计算`/usr`目录大小时还出现超时问题。这是因为`getTotalSizeOfFilesInDir()`方法会阻塞线程池,导致潜在的“池诱导死锁”。 ### 2.4 改进的并发计算文件大小示例 ```java import java.io.File; import java.util.*; import java.util.concurrent.*; public class ConcurrentTotalFileSize { class SubDirectoriesAndSize { final public long size; final public List<File> subDirectories; public SubDirectoriesAndSize( final long totalSize, final List<File> theSubDirs) { size = totalSize; subDirectories = Collections.unmodifiableList(theSubDirs); } } private SubDirectoriesAndSize getTotalAndSubDirs(final File file) { long total = 0; final List<File> subDirectories = new ArrayList<File>(); if (file.isDirectory()) { final File[] children = file.listFiles(); if (children != null) for (final File child : children) { if (child.isFile()) total += child.length(); else subDirectories.add(child); } } return new SubDirectoriesAndSize(total, subDirectories); } private long getTotalSizeOfFilesInDir(final File file) throws InterruptedException, ExecutionException, TimeoutException { final ExecutorService service = Executors.newFixedThreadPool(100); try { long total = 0; final List<File> directories = new ArrayList<File>(); directories.add(file); while (!directories.isEmpty()) { final List<Future<SubDirectoriesAndSize>> partialResults = new ArrayList<Future<SubDirectoriesAndSize>>(); for (final File directory : directories) { partialResults.add( service.submit(new Callable<SubDirectoriesAndSize>() { public SubDirectoriesAndSize call() { return getTotalAndSubDirs(directory); } })); } directories.clear(); for (final Future<SubDirectoriesAndSize> partialResultFuture : partialResults) { final SubDirectoriesAndSize subDirectoriesAndSize = partialResultFuture.get(100, TimeUnit.SECONDS); directories.addAll(subDirectoriesAndSize.subDirectories); total += subDirectoriesAndSize.size; } } return total; } finally { service.shutdown(); } } public static void main(final String[] args) throws InterruptedException, ExecutionException, TimeoutException { final long start = System.nanoTime(); final long total = new ConcurrentTotalFileSize() .getTotalSizeOfFilesInDir(new File(args[0])); final long end = System.nanoTime(); System.out.println("Total Size: " + total); System.out.println("Time taken: " + (end - start) / 1.0e9); } } ``` ### 2.5 改进版本的优势 改进的并发版本解决了简单并发版本的问题,计算`/usr`目录大小时,时间从顺序版本的 18 秒多缩短到约 8 秒。它通过让每个任务返回子目录列表,避免长时间持有线程,设计更合理。 ### 2.6 线程协调流程 ```mermaid graph TD; A[开始] --> B[拆分问题为多个任务]; B --> C[在线程池中调度任务]; C --> D[等待任务结果]; D --> E[汇总结果]; E --> F[结束]; ``` ## 3. 使用 CountDownLatch 进行线程协调 虽然`Future`在获取任务结果和协调线程方面有一定作用,但对于无返回结果的任务,它作为协调工具并不理想。而`CountDownLatch`可以在这种情况下发挥作用。 ### 3.1 简单并发代码的问题与改进思路 `NaivelyConcurrentTotalFileSize`代码简单,但存在每个线程等待其调度任务完成的问题。为了保持代码简单并使其正常工作,我们可以让每个线程更新一个共享变量,同时使用`CountDownLatch`来确保主线程等待所有子目录被访问。 ### 3.2 使用 CountDownLatch 的并发计算文件大小示例 ```java import java.io.File; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; public class ConcurrentTotalFileSizeWLatch { private ExecutorService service; final private AtomicLong pendingFileVisits = new AtomicLong(); final private AtomicLong totalSize = new AtomicLong(); final private CountDownLatch latch = new CountDownLatch(1); private void updateTotalSizeOfFilesInDir(final File file) { long fileSize = 0; if (file.isFile()) fileSize = file.length(); else { final File[] children = file.listFiles(); if (children != null) { for (final File child : children) { if (child.isFile()) fileSize += child.length(); else { pendingFileVisits.incrementAndGet(); service.execute(new Runnable() { public void run() { updateTotalSizeOfFilesInDir(child); } }); } } } } totalSize.addAndGet(fileSize); if (pendingFileVisits.decrementAndGet() == 0) latch.countDown(); } private long getTotalSizeOfFile(final String fileName) throws InterruptedException { service = Executors.newFixedThreadPool(100); pendingFileVisits.incrementAndGet(); try { updateTotalSizeOfFilesInDir(new File(fileName)); latch.await(100, TimeUnit.SECONDS); return totalSize.longValue(); } finally { service.shutdown(); } } public static void main(final String[] args) throws InterruptedException { final long start = System.nanoTime(); final long total = new ConcurrentTotalFileSizeWLatch() .getTotalSizeOfFile(args[0]); final long end = System.nanoTime(); System.out.println("Total Size: " + total); System.out.println("Time taken: " + (end - start) / 1.0e9); } } ``` ### 3.3 代码说明 - `pendingFileVisits`:用于记录仍需访问的文件数量。 - `totalSize`:用于记录文件的总大小,使用`AtomicLong`确保线程安全。 - `latch`:用于主线程等待所有子目录被访问。 ### 3.4 执行流程 | 步骤 | 操作 | | ---- | ---- | | 1 | 创建线程池和`CountDownLatch`。 | | 2 | 启动任务,更新`pendingFileVisits`和`totalSize`。 | | 3 | 当`pendingFileVisits`为 0 时,调用`latch.countDown()`。 | | 4 | 主线程等待`latch`释放。 | | 5 | 关闭线程池。 | ### 3.5 使用 CountDownLatch 的线程协调流程 ```mermaid graph TD; A[开始] --> B[初始化 CountDownLatch 和线程池]; B --> C[启动任务并更新共享变量]; C --> D{所有任务完成?}; D -- 否 --> C; D -- 是 --> E[释放 CountDownLatch]; E --> F[主线程继续执行]; F --> G[关闭线程池]; G --> H[结束]; ``` ## 4. 总结 ### 4.1 不同方法的对比 | 方法 | 优点 | 缺点 | 适用场景 | | ---- | ---- | ---- | ---- | | 顺序计算 | 代码简单 | 速度慢 | 目录结构简单,文件数量少 | | 简单并发计算 | 尝试并发处理 | 可能导致线程池阻塞和超时问题 | 不适用复杂目录结构 | | 改进的并发计算 | 避免线程长时间阻塞,性能提升 | 代码复杂度增加 | 复杂目录结构 | | 使用 CountDownLatch 的并发计算 | 代码简单,能正常工作 | 引入共享变量 | 任务无返回结果,需要协调线程 | ### 4.2 关键要点回顾 - 使用`ExecutorService`管理线程池,可配置线程池类型和任务等待队列大小。 - 任务调度可使用`execute()`、`submit()`、`invokeAll()`和`invokeAny()`方法。 - 线程协调可使用`Future`和`CountDownLatch`。 - 设计时应关注任务完成,而非线程或服务的终止。 ### 4.3 实践建议 在实际应用中,应根据具体场景选择合适的方法。对于简单问题,可使用顺序计算;对于复杂问题,可考虑使用改进的并发计算或使用`CountDownLatch`的并发计算。同时,要注意线程安全和资源管理,避免出现死锁等问题。
corwn 最低0.47元/天 解锁专栏
赠100次下载
点击查看下一篇
profit 400次 会员资源下载次数
profit 300万+ 优质博客文章
profit 1000万+ 优质下载资源
profit 1000万+ 优质文库回答
复制全文

相关推荐

李_涛

知名公司架构师
拥有多年在大型科技公司的工作经验,曾在多个大厂担任技术主管和架构师一职。擅长设计和开发高效稳定的后端系统,熟练掌握多种后端开发语言和框架,包括Java、Python、Spring、Django等。精通关系型数据库和NoSQL数据库的设计和优化,能够有效地处理海量数据和复杂查询。
最低0.47元/天 解锁专栏
赠100次下载
百万级 高质量VIP文章无限畅学
千万级 优质资源任意下载
千万级 优质文库回答免费看

最新推荐

Ruby企业版与Capistrano部署案例

# Ruby企业版与Capistrano部署案例 ## 1. 使用Ruby企业版 ### 1.1 简介 Ruby企业版(REE)是面向服务器的官方Ruby解释器发行版,它基于标准的C Ruby 1.8.7解释器,在垃圾回收、内存分配、调试和线程子系统方面有所改进。使用REE的应用程序相比标准Ruby解释器,内存使用量更少。 ### 1.2 安装与配置 以下是安装和配置REE并设置Passenger使用它的步骤: 1. 准备一个没有安装Ruby的新虚拟机: ```bash $ cd ~/deployingrails/ && mkdir vagrant_ree $ cd vagrant_ree

NC5X多子表单据API设计精要:打造高效、易用接口的专业指南

![NC5X多子表单据开发过程及代码示例](https://ioc.xtec.cat/materials/FP/Recursos/fp_dam_m02_/web/fp_dam_m02_htmlindex/WebContent/u5/media/esquema_empresa_mysql.png) # 摘要 随着软件复杂性的增加,API设计成为构建高效、可靠软件系统的关键环节。本文围绕NC5X多子表单据API的设计展开深入探讨,涵盖了基础理论、实践技巧、安全性和性能优化,以及测试与维护。文中首先介绍了RESTful API设计原则和多子表单据数据结构理论,随后提出了一系列API设计的实践技巧,

【教育互动新工具】:MATLAB GUI在教育中的创新应用与开发

![【教育互动新工具】:MATLAB GUI在教育中的创新应用与开发](https://i2.hdslb.com/bfs/archive/f8d234f872e34e624e92ca3748f0ae6d2afa9131.jpg@960w_540h_1c.webp) # 摘要 MATLAB GUI作为一种强大的教学工具,在现代教育领域发挥着越来越重要的作用。本文首先介绍了MATLAB GUI的基本概念及其在教育中的重要性。随后,文章深入探讨了MATLAB GUI的基础开发技巧,包括界面设计、高级控件应用和代码优化与调试。本文还分析了MATLAB GUI在不同学科教育中的创新应用实例,如物理模拟

数据平台的转型、优化与文化建设

# 数据平台的转型、优化与文化建设 ## 1. 转型阶段:奠定基础 在将首个用例投入生产后,接下来的重点便是扩展规模、增加更多数据域并优化架构。此时,清晰把握整体局势至关重要。以下是此阶段的关键要点: ### 1.1 明确业务能力 - 清晰了解业务能力,包括人员、流程和技术的协同。 - 明确各数据域所拥有的应用程序及其职责。 - 知晓潜在新数据产品可服务的新用例。 ### 1.2 选择合适的拓扑结构 - 定义适合组织的数据域和着陆区拓扑。 - 协调包含数据处理、存储、编目、元数据发布和策略执行等服务的蓝图。 - 研究数据域之间的数据流量,做出设计决策: - 若多个数据域

傅里叶级数:性质、运算与应用

### 傅里叶级数:性质、运算与应用 #### 1. 半波整流正弦信号 半波整流是从电力公司提供的正弦信号中获取非零直流电平信号的另一种方法。与全波整流正弦信号(FWRS)的主要区别在于,半波整流正弦信号(HWRS)输出的基频是FWRS基频的一半。这使得在直流电源应用中,更难以平滑纹波并实现恒定输出。 #### 2. 傅里叶级数的运算 在信号处理中,对信号进行操作时,需要预测信号的变化。傅里叶级数表示周期性信号的一个重要优点是,对信号 \(x(t)\) 的操作通常对应着对傅里叶系数的简单操作。以下是几种常见的操作: - **缩放或添加常数** - **缩放**:将周期性信号 \(x

【解决兼容性问题】:WinForm内嵌ECharts跨环境一致性的解决方案

![winform与内嵌echarts的数据交互,让数据动起来.rar](https://docs.devexpress.com/AspNet/images/aspxdataview-databinding-schema122370.png) # 摘要 WinForm与ECharts的结合为桌面应用程序提供了一个强大的可视化解决方案。本文首先介绍了WinForm和ECharts的基础知识,然后着重分析了在WinForm中内嵌ECharts时可能遭遇的兼容性问题,包括跨浏览器的兼容性挑战以及Windows平台特有的问题。为了克服这些挑战,本文提供了理论基础和实践操作步骤,详细介绍了兼容性问题的

【数据迁移的高效工具】:比较Excel与Oracle建表语句生成器的优劣

![【数据迁移的高效工具】:比较Excel与Oracle建表语句生成器的优劣](https://www.gemboxsoftware.com/spreadsheet/examples/106/content/DataValidation.png) # 摘要 本文全面概述了数据迁移过程中的关键环节和工具应用,重点分析了Excel数据管理、Oracle数据库建表语句生成器的实际应用,并对两者的功能、性能和用户体验进行了比较评估。文章还探讨了数据清洗、预处理及迁移实施策略,以确保数据迁移的高效性和准确性。最后,对未来数据迁移技术的发展趋势进行了展望,特别强调了新兴技术如人工智能和大数据技术对数据迁

商业信息管理与营销术语解析

# 商业信息管理与营销术语解析 ## 1. 核心概念概述 在商业运营与信息管理领域,存在着众多关键概念,这些概念对于企业的决策、运营和营销等方面起着至关重要的作用。以下为大家介绍一些常见且重要的概念。 ### 1.1 数据相关概念 - **数据挖掘**:是一种高级分析方法,用于确定数据中的特定模式,常与预测分析相关联。通过对大量数据的深入分析,挖掘出潜在的规律和趋势,为企业决策提供有力支持。 - **数据集市**:通常指一个物理平台,用于存储汇总数据以支持决策。它一般由单个组织或用户组用于特定的分析目的,能够提供针对性的数据支持。 - **数据仓库**:是集成数据的集合,用于决策制定。它是

Java UDP高级应用:掌握UDP协议高级特性的9个技巧

![Java UDP高级应用:掌握UDP协议高级特性的9个技巧](https://cheapsslsecurity.com/blog/wp-content/uploads/2022/06/what-is-user-datagram-protocol-udp.png) # 摘要 UDP协议作为一种无连接的网络传输协议,在实时应用和多播通信中表现出色。本文首先介绍了UDP协议的基础知识,随后深入探讨了其高级特性,如多播通信机制、安全特性以及高效数据传输技术。通过对多播地址和数据报格式的解析、多播组的管理和数据加密认证方法的讨论,文章强调了UDP在构建可靠通信中的重要性。本文还通过实例分析了Jav

MISRA C 2023与模块化开发:构建可维护系统架构的关键步骤

# 摘要 本文旨在探讨MISRA C 2023标准在模块化开发中的应用及其集成。首先概述MISRA C 2023标准,随后深入模块化开发的基础知识,包括模块化设计原则、编程技术及对系统架构的影响。第三章着重分析MISRA C 2023编码规则与模块化设计的关系,以及它在模块测试和集成中的作用。接着,通过不同领域的实践案例研究,展示了模块化开发的具体实现和挑战。最后一章展望了模块化开发的未来趋势,以及MISRA C 2023标准如何适应新技术的发展。本文为软件开发人员和系统架构师提供了一个全面的参考框架,帮助他们在维护系统可维护性的同时,运用MISRA C 2023标准确保代码质量和安全性。