活动介绍

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文章无限畅学
千万级 优质资源任意下载
千万级 优质文库回答免费看

最新推荐

【代码与图表的结合】:WinForm中管理ECharts图表的高效编程实践

![【代码与图表的结合】:WinForm中管理ECharts图表的高效编程实践](https://docs.devexpress.com/AspNet/images/aspxdataview-databinding-schema122370.png) # 摘要 本文深入探讨了WinForm应用程序中集成ECharts图表的全过程,包括图表的选择、下载、集成、初始化、个性化定制、动态交互以及性能优化等方面。文中详细阐述了ECharts图表的特性与优势,集成步骤,以及如何在WinForm中实现图表的定制、交互功能、动态更新和数据处理。进一步,本文还着重讨论了在大数据集下优化ECharts图表渲染

【数据迁移与整合的高效方法】:Excel到Oracle建表语句生成器的深度解析

![【数据迁移与整合的高效方法】:Excel到Oracle建表语句生成器的深度解析](https://img-blog.csdnimg.cn/20190110103854677.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNjY4ODUxOQ==,size_16,color_FFFFFF,t_70) # 摘要 本文综合论述了数据迁移与整合的过程,从Excel数据处理基础讲起,涵盖基本操作、高级技术以及与Orac

D语言的Web、GUI编程及其他实用技巧

### D语言的Web、GUI编程及其他实用技巧 #### 一、Web和GUI编程相关资源 在Web和GUI编程方面,有一些实用的资源可供使用: - http://code.dlang.org/packages/derelict-sdl2 提供了对SDL(C Simple Directmedia Layer库,被多个图形应用使用)的动态绑定。 - http://code.dlang.org/packages/gtk-d 为GTK(跨平台GUI工具包)提供了面向对象的包装器。 #### 二、创建OpenGL窗口 可以使用相关模块创建一个带有彩色旋转金字塔的OpenGL窗口,以下是具体步骤:

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

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

住宅房产估值模型的比较分析

### 住宅房产估值模型的比较分析 在房地产评估领域,准确评估住宅房产价值至关重要。传统的销售比较法虽常用,但依赖评估师的主观判断和经验。随着科技发展,自动化估值模型(AVMs)应运而生,其基于多元回归分析、软计算和地理信息系统(GIS)等技术,为评估工作提供了更客观的支持。本文将探讨使用KEEL、RapidMiner和WEKA这三款流行的数据挖掘系统,对常见机器学习算法构建住宅房产估值模型进行比较分析。 #### 1. 引言 销售比较法是确定房产市场价值的常用方法。运用该方法时,需有与被评估房产属性相似的交易价格数据。若有优质的可比交易,就能获得可靠估值。然而,评估师在评估前需综合利用地

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设计的实践技巧,

布尔电路核心函数分析与架构研究

### 布尔电路核心函数分析与架构研究 #### 1. 基础概念与输入分析 在布尔电路相关研究中,有几个重要的基础概念。首先,$nijk$ 表示二进制代码 $<nij1 nij2...nijm>$ 的第 $k$ 个分量,该二进制代码代表变量 $xij$ 的名称。为了确定表示变量名称所需的二进制位数 $m$,需要变量数量 $nv$ 的二进制代码,规则是 $m$ 为不小于 $log_2 nv$ 的最小整数,且分隔符的二进制代码不包含在变量数量的二进制代码中。 忽略变量数量的二进制代码位和分隔符位后,磁带上的输入位数 $n$ 可由公式 $n = t * 3 * (1 +$ 不小于 $log_2

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

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

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

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

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