提交 7beb237b 编写于 作者: W wizardforcel

2021-10-11 22:02:16

上级 62c57f02
# 序言
# 零、序言
如今,计算机系统(以及其他相关系统,如平板电脑或智能手机)允许您同时执行多项任务。这是可能的,因为它们拥有同时控制多个任务的并发操作系统。如果使用您最喜欢的编程语言的并发 API,您还可以让一个应用程序执行多个任务(读取文件、显示消息或通过网络读取数据)。Java 包含一个非常强大的并发 API,它允许您轻松实现任何类型的并发应用程序。此 API 增加了每个版本中为程序员提供的功能。现在,在 Java8 中,它包含了流 API 和新的方法和类,以方便并发应用程序的实现。本书涵盖了 Java 并发 API 的最重要元素,向您展示了如何在实际应用程序中使用它们。这些要素如下:
如今,计算机系统(以及其他相关系统,如平板电脑或智能手机)允许您同时执行多项任务。这是可能的,因为它们拥有同时控制多个任务的并发操作系统。如果使用您最喜欢的编程语言的并发 API,您还可以让一个应用执行多个任务(读取文件、显示消息或通过网络读取数据)。Java 包含一个非常强大的并发 API,它允许您轻松实现任何类型的并发应用。此 API 增加了每个版本中为程序员提供的功能。现在,在 Java8 中,它包含了流 API 和新的方法和类,以方便并发应用的实现。本书涵盖了 Java 并发 API 的最重要元素,向您展示了如何在实际应用中使用它们。这些要素如下:
* 执行器框架,用于控制大量任务的执行
* Phaser 类,用于执行可分为阶段的任务
* Fork/Join 框架,用于执行使用分治技术解决问题的任务
* 流 API,用于处理大型数据源
* 并发数据结构,用于在并发应用程序中存储数据
* 并发数据结构,用于在并发应用中存储数据
* 同步机制,用于组织并发任务
但是,它还包括更多内容:设计并发应用程序的方法、设计模式、实现良好并发应用程序的技巧和技巧,以及测试并发应用程序的工具和技术。
但是,它还包括更多内容:设计并发应用的方法、设计模式、实现良好并发应用的技巧和技巧,以及测试并发应用的工具和技术。
# 这本书涵盖的内容
[第一章](01.html#DB7S2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 1. The First Step – Concurrency Design Principles")*第一步——并发设计原则*将教您并发应用程序的设计原则。他们还将学习并发应用程序可能存在的问题,以及设计并发应用程序的方法,然后学习一些设计模式、技巧和技巧。
[第一章](01.html#DB7S2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 1. The First Step – Concurrency Design Principles")*第一步——并发设计原则*将教您并发应用的设计原则。他们还将学习并发应用可能存在的问题,以及设计并发应用的方法,然后学习一些设计模式、技巧和技巧。
[第 2 章](02.html#KVCC1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 2. Managing Lots of Threads – Executors")*管理大量线程–执行器*,将教您执行器框架的基本原理。该框架允许您使用大量线程,而无需创建或管理它们。您将实现 k-最近邻算法和一个基本的客户机/服务器应用程序
[第 2 章](02.html#KVCC1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 2. Managing Lots of Threads – Executors")*管理大量线程–执行器*,将教您执行器框架的基本原理。该框架允许您使用大量线程,而无需创建或管理它们。您将实现 k-最近邻算法和一个基本的客户机/服务器应用。
[第三章](03.html#QMFO1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 3. Getting the Maximum from Executors")*从执行者*获取最大值,将教您执行者的一些高级特性,包括取消和调度任务,以在延迟后或每隔一定时间执行任务。您将实现一个高级客户机/服务器应用程序和一个新闻阅读器。
[第三章](03.html#QMFO1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 3. Getting the Maximum from Executors")*从执行者*获取最大值,将教您执行者的一些高级特性,包括取消和调度任务,以在延迟后或每隔一定时间执行任务。您将实现一个高级客户机/服务器应用和一个新闻阅读器。
[第 4 章](04.html#VF2I1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 4. Getting Data from the Tasks – The Callable and Future Interfaces")*从任务中获取数据–可调用和未来接口*将教会您如何在执行者中处理使用可调用和未来接口返回结果的任务。您将实现一个最佳匹配算法和一个应用程序来构建反向索引。
[第 4 章](04.html#VF2I1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 4. Getting Data from the Tasks – The Callable and Future Interfaces")*从任务中获取数据–可调用和未来接口*将教会您如何在执行者中处理使用可调用和未来接口返回结果的任务。您将实现一个最佳匹配算法和一个应用来构建反向索引。
[第 5 章](05.html#1394Q1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 5. Running Tasks Divided into Phases – The Phaser Class")*运行分阶段的任务–Phaser 类*将教您如何使用 Phaser 类并行执行可分阶段的任务。您将实现关键字提取算法和遗传算法。
[第 6 章](06.html#173722-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 6. Optimizing Divide and Conquer Solutions – The Fork/Join Framework")*优化分治解决方案–Fork/Join 框架*将教您如何使用一种特殊类型的执行器,该执行器通过分治技术可以解决的问题进行优化:Fork/Join 框架及其偷功算法。您将实现 k-means 聚类算法、数据过滤算法和合并排序算法。
[第 7 章](07.html#1CQAE2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 7. Processing Massive Datasets with Parallel Streams – The Map and Reduce Model")*使用并行流处理海量数据集–映射和简化模型*将教您如何使用流处理大型数据集。在本章中,您将学习如何使用 stream API 和 streams 的更多功能实现映射和缩减应用程序。您将实现一个数字摘要算法和一个信息检索搜索工具。
[第 7 章](07.html#1CQAE2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 7. Processing Massive Datasets with Parallel Streams – The Map and Reduce Model")*使用并行流处理海量数据集–映射和简化模型*将教您如何使用流处理大型数据集。在本章中,您将学习如何使用 stream API 和 streams 的更多功能实现映射和缩减应用。您将实现一个数字摘要算法和一个信息检索搜索工具。
[第 8 章](08.html#1GKCM1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 8. Processing Massive Datasets with Parallel Streams – The Map and Collect Model")*处理具有并行流的海量数据集–映射和收集模型*,将教您如何使用流 API 的 Collect()方法将数据流可变地简化为不同的数据结构,包括收集器类中定义的预定义收集器。您将实现一个无索引搜索数据的工具、一个推荐系统和一个计算社交网络中两个人的常见联系人列表的算法。
[第 9 章](09.html#1LCVG2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 9. Diving into Concurrent Data Structures and Synchronization Utilities")*深入探讨并发数据结构和同步实用程序,*将教您如何使用最重要的并发数据结构(可以在并发应用程序中使用的数据结构,而不会造成数据争用情况)以及 Java 并发 API 中包含的所有同步机制来组织任务的执行。
[第 9 章](09.html#1LCVG2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 9. Diving into Concurrent Data Structures and Synchronization Utilities")*深入探讨并发数据结构和同步工具,*将教您如何使用最重要的并发数据结构(可以在并发应用中使用的数据结构,而不会造成数据争用情况)以及 Java 并发 API 中包含的所有同步机制来组织任务的执行。
[第 10 章](10.html#1O8H61-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 10. Integration of Fragments and Implementation of Alternatives")*片段集成和备选方案的实现*将教您如何使用共享内存或消息传递,使用并发应用程序片段自身的并发技术实现大型应用程序。您还将学习本书中示例的不同实现替代方案。
[第 10 章](10.html#1O8H61-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 10. Integration of Fragments and Implementation of Alternatives")*片段集成和备选方案的实现*将教您如何使用共享内存或消息传递,使用并发应用片段自身的并发技术实现大型应用。您还将学习本书中示例的不同实现替代方案。
[第 11 章](11.html#1S2JE2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 11. Testing and Monitoring Concurrent Applications")*测试和监控并发应用程序*教您如何获取一些 Java 并发 API 元素(线程、锁、执行器等)的状态信息。您还将学习如何使用 Java VisualVM 应用程序监视并发应用程序,以及如何使用多线程 DC 库和 Java Pathfinder 应用程序测试并发应用程序
[第 11 章](11.html#1S2JE2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 11. Testing and Monitoring Concurrent Applications")*测试和监控并发应用*教您如何获取一些 Java 并发 API 元素(线程、锁、执行器等)的状态信息。您还将学习如何使用 Java VisualVM 应用监视并发应用,以及如何使用多线程 DC 库和 Java Pathfinder 应用测试并发应用
# 这本书你需要什么
......@@ -41,7 +41,7 @@
# 这本书是给谁的
如果您是一名 Java 开发人员,了解并发编程的基本原理,但希望获得 Java 并发 API 的专家知识,以开发利用计算机所有硬件资源的优化应用程序,那么本书适合您。
如果您是一名 Java 开发人员,了解并发编程的基本原理,但希望获得 Java 并发 API 的专家知识,以开发利用计算机所有硬件资源的优化应用,那么本书适合您。
# 公约
......
# 第 2 章管理大量线程–执行器
# 二、管理大量线程——执行器
当您实现一个简单的并发应用程序时,您将为每个并发任务创建并执行一个线程。这种方法可能有一些重要的问题。自**Java 版本 5**以来,Java 并发 API 包含**executor framework**以提高并发任务较多的并发应用程序的性能。在本章中,我们将介绍以下内容:
当您实现一个简单的并发应用时,您将为每个并发任务创建并执行一个线程。这种方法可能有一些重要的问题。自**Java 版本 5**以来,Java 并发 API 包含**executor framework**以提高并发任务较多的并发应用的性能。在本章中,我们将介绍以下内容:
* 遗嘱执行简介
* 遗嘱执行简介
* 第一个例子——k-最近邻算法
* 第二个示例—客户机/服务器环境中的并发性
# 遗嘱执行简介
# 遗嘱执行简介
在 Java 中实现并发应用程序的基本机制是:
在 Java 中实现并发应用的基本机制是:
* **实现可运行接口**的类:这是您希望以并发方式实现的代码
* **Thread 类**的实例:这是将以并发方式执行代码的线程
通过这种方法,您负责创建和管理`Thread` 对象,并实现线程之间的同步机制。但是,它可能会有一些问题,特别是对于那些有大量并发任务的应用程序。如果创建太多线程,可能会降低应用程序的性能,甚至会挂起整个系统。
通过这种方法,您负责创建和管理`Thread` 对象,并实现线程之间的同步机制。但是,它可能会有一些问题,特别是对于那些有大量并发任务的应用。如果创建太多线程,可能会降低应用的性能,甚至会挂起整个系统。
Java5 包含了 executor 框架,以解决这些问题并提供一个高效的解决方案,这将比传统的并发机制更易于程序员使用。
在本章中,我们将通过使用 executor 框架实现以下两个示例来介绍 executor 框架的基本特征:
* **k-最近邻算法**:这是在分类中使用的一种基本的**机器学习**算法。它根据列车数据集中*k*最相似示例的标记确定测试示例的标记。
* **客户机/服务器环境中的并发**:为数千或数百万客户机提供信息的应用程序如今至关重要。必须以最佳方式实现系统的服务器端。
* **客户机/服务器环境中的并发**:为数千或数百万客户机提供信息的应用如今至关重要。必须以最佳方式实现系统的服务器端。
[第 3 章](03.html#QMFO1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 3. Getting the Maximum from Executors")*从执行器获取最大值*[第 4 章](04.html#VF2I1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 4. Getting Data from the Tasks – The Callable and Future Interfaces")*从任务获取数据–可调用和未来接口*中,我们将介绍执行器的更高级方面。
## 执行的基本特征
## 执行的基本特征
执行的主要特征是:
执行的主要特征是:
* 您不需要创建任何`Thread`对象。如果要执行并发任务,只需创建任务的实例(例如,实现`Runnable`接口的类)并将其发送给执行者。它将管理执行任务的线程。
* 执行器减少了线程创建和重用线程所带来的开销。在内部,它管理一个名为**工作线程**的线程池。如果您向执行器发送任务,且工作线程处于空闲状态,则执行器将使用该线程执行该任务。
* 很容易控制执行器使用的资源。您可以限制执行器的最大工作线程数。如果发送的任务多于工作线程,则执行器将它们存储在队列中。当工作线程完成任务的执行时,它会从队列中取出另一个。
* 您必须显式地完成执行器的执行。您必须向执行器指示它必须完成其执行并终止创建的线程。如果您不这样做,它将不会完成其执行,您的应用程序也不会结束。
* 您必须显式地完成执行器的执行。您必须向执行器指示它必须完成其执行并终止创建的线程。如果您不这样做,它将不会完成其执行,您的应用也不会结束。
执行者具有更有趣的特征,使他们非常强大和灵活。
......@@ -43,7 +43,7 @@ executor 框架具有各种接口和类,这些接口和类实现了 executor
* **ExecutorService 接口**:该接口对`Executor`接口进行了扩展,增加了框架功能的方法,如:
* 执行返回结果的任务:`Runnable`接口提供的`run()`方法不返回结果,但对于执行者,您可以有返回结果的任务
* 使用单个方法调用执行任务列表
* 完成执行的执行并等待其终止
* 完成执行的执行并等待其终止
* **ThreadPoolExecutor 类**:这个类实现了`Executor``ExecutorService`接口。此外,它还包括一些获取执行器状态的附加方法(工作线程数、已执行任务数等)、建立执行器参数的方法(最小和最大工作线程数、空闲线程等待新任务的时间等)以及允许程序员扩展和调整其功能的方法。
* **Executors 类**:该类提供实用方法来创建`Executor`对象及其他相关类。
......@@ -176,7 +176,7 @@ public class KnnClassifierParallelIndividual {
}
```
为了创建执行器,我们使用了`Executors`实用程序类及其`newFixedThreadPool()`方法。此方法接收您希望在执行器中拥有的工作线程数。执行器的工作线程数永远不会超过您在构造函数中指定的数目。此方法返回一个`ExecutorService`对象,但我们将其强制转换为`ThreadPoolExecutor`对象,以访问提供的方法,但该类不包括在接口中。
为了创建执行器,我们使用了`Executors`工具类及其`newFixedThreadPool()`方法。此方法接收您希望在执行器中拥有的工作线程数。执行器的工作线程数永远不会超过您在构造函数中指定的数目。此方法返回一个`ExecutorService`对象,但我们将其强制转换为`ThreadPoolExecutor`对象,以访问提供的方法,但该类不包括在接口中。
此类还实现了接收示例并返回字符串的`classify()`方法。
......@@ -214,7 +214,7 @@ public class KnnClassifierParallelIndividual {
最后,我们计算分配给输入示例的标记。此代码与串行版本中的代码相同。
`KnnClassifierParallelIndividual`类还包括一个方法,用于关闭调用其`shutdown()`方法的执行器。如果不调用此方法,应用程序将永远不会结束,因为由执行器创建的线程仍然处于活动状态,并等待新任务执行。执行以前提交的任务,拒绝新提交的任务。该方法不会等待执行器完成,它会立即返回:
`KnnClassifierParallelIndividual`类还包括一个方法,用于关闭调用其`shutdown()`方法的执行器。如果不调用此方法,应用将永远不会结束,因为由执行器创建的线程仍然处于活动状态,并等待新任务执行。执行以前提交的任务,拒绝新提交的任务。该方法不会等待执行器完成,它会立即返回:
```java
public void destroy() {
......@@ -382,11 +382,11 @@ K
# 第二个示例–客户机/服务器环境中的并发性
**客户机/服务器模式**是一种软件架构,其中应用程序分为两部分:提供资源(数据、操作、打印机、存储等)的服务器部分和使用服务器提供的资源的客户机部分。传统上,这种体系结构用于企业界,但随着互联网的繁荣,它仍然是一个实际的话题。您可以将 web 应用程序视为客户机/服务器应用程序,其中服务器部分是在 web 服务器中执行的应用程序的后端部分,web navigator 执行应用程序的客户机部分。**SOA**(简称**面向服务架构**)是客户机/服务器架构的另一个示例,其中公开的 web 服务是服务器部分,使用它们的不同客户机是客户机部分。
**客户机/服务器模式**是一种软件架构,其中应用分为两部分:提供资源(数据、操作、打印机、存储等)的服务器部分和使用服务器提供的资源的客户机部分。传统上,这种体系结构用于企业界,但随着互联网的繁荣,它仍然是一个实际的话题。您可以将 web 应用视为客户机/服务器应用,其中服务器部分是在 web 服务器中执行的应用的后端部分,web navigator 执行应用的客户机部分。**SOA**(简称**面向服务架构**)是客户机/服务器架构的另一个示例,其中公开的 web 服务是服务器部分,使用它们的不同客户机是客户机部分。
在客户机/服务器环境中,我们通常有一台服务器和许多使用服务器提供的服务的客户机,因此当您必须设计其中一个系统时,服务器的性能是一个关键方面。
在本节中,我们将实现一个简单的客户机/服务器应用程序。它将在**世界银行**的世界发展指标上搜索数据,您可以从这里下载:[http://data.worldbank.org/data-catalog/world-development-indicators](http://data.worldbank.org/data-catalog/world-development-indicators) 。该数据包含 1960 年至 2014 年世界所有国家的不同指标值。
在本节中,我们将实现一个简单的客户机/服务器应用。它将在**世界银行**的世界发展指标上搜索数据,您可以从这里下载:[http://data.worldbank.org/data-catalog/world-development-indicators](http://data.worldbank.org/data-catalog/world-development-indicators) 。该数据包含 1960 年至 2014 年世界所有国家的不同指标值。
我们服务器的主要特点是:
......@@ -398,11 +398,11 @@ K
* **停止**:本次查询的格式为`z;`。服务器在收到此命令时停止执行。
* 在其他情况下,服务器返回错误消息。
与前面的示例一样,我们将向您展示如何实现此客户机/服务器应用程序的串行版本。然后,我们将向您展示如何使用执行器实现并发版本。最后,我们将比较这两种解决方案,以查看在这种情况下使用并发的优势。
与前面的示例一样,我们将向您展示如何实现此客户机/服务器应用的串行版本。然后,我们将向您展示如何使用执行器实现并发版本。最后,我们将比较这两种解决方案,以查看在这种情况下使用并发的优势。
## 客户端/服务器-串行版本
我们的服务器应用程序的串行版本有三个主要部分:
我们的服务器应用的串行版本有三个主要部分:
* **DAO**(简称**数据访问对象**部分,负责访问数据并获取查询结果
* 命令部分,由每种查询的命令组成
......@@ -412,7 +412,7 @@ K
### 刀部
正如我们前面提到的,服务器将搜索世界银行世界发展指标的数据。此数据位于 CSV 文件中。应用程序中的 DAO 组件将整个文件加载到内存中的`List`对象中。它为它将参与的每个查询实现一个方法,该方法遍历查找数据的列表。
正如我们前面提到的,服务器将搜索世界银行世界发展指标的数据。此数据位于 CSV 文件中。应用中的 DAO 组件将整个文件加载到内存中的`List`对象中。它为它将参与的每个查询实现一个方法,该方法遍历查找数据的列表。
我们这里不包括这个类的代码,因为它很容易实现,而且这不是本书的主要目的。
......@@ -454,7 +454,7 @@ public abstract class Command {
}
```
本报告在`ReportCommand`中实`execute()`方法如下:
本报告在`ReportCommand`中实`execute()`方法如下:
```java
@Override
......@@ -617,7 +617,7 @@ public class ConcurrentServer {
System.out.println("Main server thread ended");
```
我们添加了两个额外的方法:`getExecutor()`方法,返回用于执行并发任务的`ThreadPoolExecutor`对象;以及`shutdown()`方法,用于以有序方式完成服务器的执行者。调用执行`shutdown()`方法,关闭`ServerSocket`
我们添加了两个额外的方法:`getExecutor()`方法,返回用于执行并发任务的`ThreadPoolExecutor`对象;以及`shutdown()`方法,用于以有序方式完成服务器的执行者。调用执行`shutdown()`方法,关闭`ServerSocket`
```java
public static void shutdown() {
......@@ -914,7 +914,7 @@ public class ParallelCache {
#### 日志系统
在本章的所有示例中,我们使用`System.out.println()`方法在控制台中写入信息。当您实现将在生产环境中执行的企业应用程序时,最好使用日志系统来编写调试和错误信息。在 Java 中,`log4j`是最流行的日志系统。在本例中,我们将实现我们自己的日志系统,实现生产者/消费者并发设计模式。使用日志系统的任务将是生产者,而将日志信息写入文件的特殊任务(作为线程执行)将是使用者。该日志系统的组件包括:
在本章的所有示例中,我们使用`System.out.println()`方法在控制台中写入信息。当您实现将在生产环境中执行的企业应用时,最好使用日志系统来编写调试和错误信息。在 Java 中,`log4j`是最流行的日志系统。在本例中,我们将实现我们自己的日志系统,实现生产者/消费者并发设计模式。使用日志系统的任务将是生产者,而将日志信息写入文件的特殊任务(作为线程执行)将是使用者。该日志系统的组件包括:
* **LogTask**:这个类实现日志使用者,每隔 10 秒读取队列中存储的日志消息并将其写入文件。它将由一个`Thread`对象执行。
* **记录器**:这个是我们日志系统的主类。它有一个队列,生产者存储信息,消费者读取信息。它还包括向队列中添加消息的方法,以及获取队列中存储的所有消息并将其写入磁盘的方法。
......@@ -1096,16 +1096,16 @@ Java 中有两种并发数据结构:
* `poll()``pollFirst()``pollLast()`:返回并从数据结构中删除一个元素。如果为空,则返回空值。
* `peek()``peekFirst()``peekLast()`:返回,但不从数据结构中删除元素。如果为空,则返回空值。
[第 9 章](09.html#1LCVG2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 9. Diving into Concurrent Data Structures and Synchronization Utilities")*深入到并发数据结构和同步实用程序*中,我们将更详细地描述并发数据结构。
[第 9 章](09.html#1LCVG2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 9. Diving into Concurrent Data Structures and Synchronization Utilities")*深入到并发数据结构和同步工具*中,我们将更详细地描述并发数据结构。
# 总结
在简单的并发应用程序中,我们使用`Runnable`接口和`Thread`类执行并发任务。我们创建和管理线程并控制它们的执行。我们不能在大型并发应用程序中采用这种方法,因为它会给我们带来很多问题。对于这些情况,Java 并发 API 引入了 executor 框架。在本章中,我们介绍了构成该框架的基本特征和组件。首先是`Executor`接口,它定义了向执行者发送`Runnable`任务的基本方法。这个接口有一个子接口,`ExecutorService`接口,它包括发送给返回结果的执行者任务的方法(这些任务实现`Callable`接口,我们将在[第 4 章](04.html#VF2I1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 4. Getting Data from the Tasks – The Callable and Future Interfaces")中看到,*从任务获取数据–可调用和未来的接口*以及任务列表。
在简单的并发应用中,我们使用`Runnable`接口和`Thread`类执行并发任务。我们创建和管理线程并控制它们的执行。我们不能在大型并发应用中采用这种方法,因为它会给我们带来很多问题。对于这些情况,Java 并发 API 引入了 executor 框架。在本章中,我们介绍了构成该框架的基本特征和组件。首先是`Executor`接口,它定义了向执行者发送`Runnable`任务的基本方法。这个接口有一个子接口,`ExecutorService`接口,它包括发送给返回结果的执行者任务的方法(这些任务实现`Callable`接口,我们将在[第 4 章](04.html#VF2I1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 4. Getting Data from the Tasks – The Callable and Future Interfaces")中看到,*从任务获取数据–可调用和未来的接口*以及任务列表。
`ThreadPoolExecutor`类是两个接口的基本实现:添加额外的方法以获取有关执行器状态以及正在执行的线程或任务数量的信息。使用`Executors`类的不同方法创建对象是最容易的。
我们向您展示了如何使用执行器,并使用实现两个真实示例的执行器将串行算法转换为并发算法。第一个例子是 k-最近邻算法,将其应用于 UCI 机器学习库的银行营销数据集。第二个示例是一个客户机/服务器应用程序,用于查询世界银行的世界发展指标。
我们向您展示了如何使用执行器,并使用实现两个真实示例的执行器将串行算法转换为并发算法。第一个例子是 k-最近邻算法,将其应用于 UCI 机器学习库的银行营销数据集。第二个示例是一个客户机/服务器应用,用于查询世界银行的世界发展指标。
在这两种情况下,执行器的使用使我们的性能有了很大的提高。
在下一章中,我们将描述如何使用执行器实现高级技术。我们将完成我们的客户机/服务器应用程序,添加在低优先级任务之前取消和执行高优先级任务的可能性。我们还将向您展示如何实现定期执行的任务,实现 RSS 新闻阅读器。
\ No newline at end of file
在下一章中,我们将描述如何使用执行器实现高级技术。我们将完成我们的客户机/服务器应用,添加在低优先级任务之前取消和执行高优先级任务的可能性。我们还将向您展示如何实现定期执行的任务,实现 RSS 新闻阅读器。
\ No newline at end of file
# 第三章从执行人处获得最大利益
# 三、最大程度利用执行器
[第 2 章](02.html#KVCC1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 2. Managing Lots of Threads – Executors")*管理大量线程–执行器*中,我们介绍了执行器的基本特性,以此提高执行大量并发任务的并发应用程序的性能。在本章中,我们将为您的并发应用程序提供一个强大的工具,并进一步解释它们的特点。在本章中,我们将介绍以下内容:
[第 2 章](02.html#KVCC1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 2. Managing Lots of Threads – Executors")*管理大量线程–执行器*中,我们介绍了执行器的基本特性,以此提高执行大量并发任务的并发应用的性能。在本章中,我们将为您的并发应用提供一个强大的工具,并进一步解释它们的特点。在本章中,我们将介绍以下内容:
* 执行者的先进性
* 第一个示例–高级服务器应用程序
* 第一个示例–高级服务器应用
* 第二个示例–执行定期任务
* 关于遗嘱执行的其他信息
* 关于遗嘱执行的其他信息
# 执行者的先进性
......@@ -14,9 +14,9 @@ executor 是类,允许程序员执行并发任务,而不必担心线程的
* 如何创建执行器以及创建执行器时的不同选项
* 如何将并发任务发送给执行者
* 如何控制执行者使用的资源
* 执行器如何在内部使用线程池来优化应用程序的性能
* 执行器如何在内部使用线程池来优化应用的性能
然而,执行者可以给你更多的选择,使它们成为并发应用程序中的强大机制。
然而,执行者可以给你更多的选择,使它们成为并发应用中的强大机制。
## 取消任务
......@@ -56,9 +56,9 @@ executor 是类,允许程序员执行并发任务,而不必担心线程的
* `ThreadFactory`:您可以指定`ThreadFactory`接口的实现,执行者将使用该工厂创建执行任务的线程。例如,您可以使用`ThreadFactory`接口创建`Thread`类的扩展,该扩展保存有关任务执行时间的日志信息。
* `RejectedExecutionHandler`:调用`shutdown()``shutdownNow()`方法后,发送给执行者的所有任务都将被拒绝。您可以指定`RejectedExecutionHandler`接口的实现来管理这种情况。
# 第一个示例–高级服务器应用程序
# 第一个示例–高级服务器应用
[第 2 章](02.html#KVCC1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 2. Managing Lots of Threads – Executors")*管理大量线程–执行器*中,我们给出了一个客户机/服务器应用程序的示例。我们实现了一个服务器来搜索世界银行世界发展指标的数据,并实现了一个客户端,该客户端对该服务器进行多次调用,以测试执行器的性能。
[第 2 章](02.html#KVCC1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 2. Managing Lots of Threads – Executors")*管理大量线程–执行器*中,我们给出了一个客户机/服务器应用的示例。我们实现了一个服务器来搜索世界银行世界发展指标的数据,并实现了一个客户端,该客户端对该服务器进行多次调用,以测试执行器的性能。
在本节中,我们将扩展该示例以添加以下特征:
......@@ -75,7 +75,7 @@ executor 是类,允许程序员执行并发任务,而不必担心线程的
* **停止**`z;username;priority`其中`username`为用户名称,`priority`为查询优先级。
* 我们实现了一个新的查询:
* **取消**`c;username;priority`其中`username`为用户名称,`priority`为查询优先级。
* 我们已经实施了我们自己的执行人,以:
* 我们已经实现了我们自己的执行器,以:
* 计算每个用户的服务器使用率
* 按优先级执行任务
* 控制任务的拒绝
......@@ -194,7 +194,7 @@ public class ServerTask<V> extends FutureTask<V> implements Comparable<ServerTas
}
```
### 遗嘱执行
### 遗嘱执行
现在我们有了执行器的辅助类,我们必须实现执行器本身。为此,我们实现了`ServerExecutor`类。它扩展了`ThreadPoolExecutor`类,并具有一些内部属性,如下所示:
......@@ -779,7 +779,7 @@ public class NewsBuffer {
}
```
缓冲区的项目将由`NewsWriter`类写入磁盘,该类将作为独立线程执行。它只有一个指向应用程序中使用的`NewsBuffer`类的内部属性:
缓冲区的项目将由`NewsWriter`类写入磁盘,该类将作为独立线程执行。它只有一个指向应用中使用的`NewsBuffer`类的内部属性:
```java
public class NewsWriter implements Runnable {
......@@ -844,7 +844,7 @@ public class NewsTask implements Runnable {
}
```
在本例中,我们还实现了另一个线程来实现执行器和任务的初始化以及等待执行的最终完成。我们将这个类命名为`NewsSystem`。它有三个内部属性来存储 RSS 源文件的路径、存储新闻的缓冲区和控制执行结束的`CountDownLatch`对象。`CountDownLatch`类是一种同步机制,允许您让线程等待事件。我们将在[第 9 章](09.html#1LCVG2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 9. Diving into Concurrent Data Structures and Synchronization Utilities")中详细介绍此类的使用,*将深入探讨并发数据结构和同步实用程序*。我们有以下代码:
在本例中,我们还实现了另一个线程来实现执行器和任务的初始化以及等待执行的最终完成。我们将这个类命名为`NewsSystem`。它有三个内部属性来存储 RSS 源文件的路径、存储新闻的缓冲区和控制执行结束的`CountDownLatch`对象。`CountDownLatch`类是一种同步机制,允许您让线程等待事件。我们将在[第 9 章](09.html#1LCVG2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 9. Diving into Concurrent Data Structures and Synchronization Utilities")中详细介绍此类的使用,*将深入探讨并发数据结构和同步工具*。我们有以下代码:
```java
public class NewsSystem implements Runnable {
......@@ -968,7 +968,7 @@ public class Timer {
}
```
接下来,我们必须执行执行的内部任务。当您向执行者发送一个`Runnable`对象时,在外部,您将该对象视为并发任务,但执行者将该对象转换为另一个任务,即`FutureTask`类的实例,该类包括执行任务的`run()`方法和管理任务执行的`Future`接口的方法。为了实现这个示例,我们必须实现一个扩展`FutureTask`类的类,并且,由于我们将在**调度执行器**中执行这些任务,它必须实现`RunnableScheduledFuture`接口。此接口提供了返回下一次执行任务剩余时间的`getDelay()`方法。我们已经在`ExecutorTask`课上完成了这些内部任务。它有四个内部属性:
接下来,我们必须执行执行的内部任务。当您向执行者发送一个`Runnable`对象时,在外部,您将该对象视为并发任务,但执行者将该对象转换为另一个任务,即`FutureTask`类的实例,该类包括执行任务的`run()`方法和管理任务执行的`Future`接口的方法。为了实现这个示例,我们必须实现一个扩展`FutureTask`类的类,并且,由于我们将在**调度执行器**中执行这些任务,它必须实现`RunnableScheduledFuture`接口。此接口提供了返回下一次执行任务剩余时间的`getDelay()`方法。我们已经在`ExecutorTask`课上完成了这些内部任务。它有四个内部属性:
* `ScheduledThreadPoolExecutor`类创建的原始`RunnableScheduledFuture`内部任务
* 将执行任务的计划执行者
......@@ -1070,11 +1070,11 @@ public class NewsExecutor extends ScheduledThreadPoolExecutor {
}
```
我们必须实现其他版本的`NewsSystem``Main`类来使用`NewsExecutor`。为此,我们实`NewsAdvancedSystem``AdvancedMain`
我们必须实现其他版本的`NewsSystem``Main`类来使用`NewsExecutor`。为此,我们实`NewsAdvancedSystem``AdvancedMain`
现在您可以运行高级新闻系统来查看执行之间的延迟时间是如何变化的。
# 关于执行的其他信息
# 关于执行的其他信息
在本章中,我们扩展了`ThreadPoolExecutor``ScheduledThreadPoolExecutor`类,并重写了它们的一些方法。但是,如果需要更特殊的行为,可以重写更多的方法。以下是一些可以覆盖的方法:
......
# 第 4 章。从任务获取数据–可调用接口和未来接口
# 四、从任务获取数据——`Runnable`接口和`Future`接口
[第 2 章](02.html#KVCC1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 2. Managing Lots of Threads – Executors")*管理大量线程–执行器、*[第 3 章](03.html#QMFO1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 3. Getting the Maximum from Executors")*从执行器*获取最大值,我们介绍了 executor 框架以提高并发应用程序的性能,并向您展示了如何实现高级特性以使此框架适应您的需要。在这些章节中,执行者执行的所有任务都基于`Runnable`接口及其不返回值的`run()`方法。但是,executor 框架允许我们执行其他类型的任务,这些任务返回基于`Callable``Future`接口的结果。在本章中,我们将介绍以下主题:
[第 2 章](02.html#KVCC1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 2. Managing Lots of Threads – Executors")*管理大量线程–执行器、*[第 3 章](03.html#QMFO1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 3. Getting the Maximum from Executors")*从执行器*获取最大值,我们介绍了 executor 框架以提高并发应用的性能,并向您展示了如何实现高级特性以使此框架适应您的需要。在这些章节中,执行者执行的所有任务都基于`Runnable`接口及其不返回值的`run()`方法。但是,executor 框架允许我们执行其他类型的任务,这些任务返回基于`Callable``Future`接口的结果。在本章中,我们将介绍以下主题:
* `Callable``Future`接口介绍
* 第一个示例–单词的最佳匹配算法
......@@ -668,11 +668,11 @@ public class ExistBasicConcurrentCalculation {
搜索文档集合中的单词或单词列表时,可以使用反向索引获取与每个单词关联的文档列表,并使用搜索结果创建唯一列表。
在本节中,您将学习如何使用 Java 并发实用程序为文档集合构造反向索引文件。作为文档集合,我们使用包含电影信息的维基百科页面构建了一组 100673 个文档。我们已将每个维基百科页面转换为文本文件。您可以下载此文档集以及有关该书的所有信息。
在本节中,您将学习如何使用 Java 并发工具为文档集合构造反向索引文件。作为文档集合,我们使用包含电影信息的维基百科页面构建了一组 100673 个文档。我们已将每个维基百科页面转换为文本文件。您可以下载此文档集以及有关该书的所有信息。
为了构造反向索引,我们不删除任何单词,也不使用任何词干算法。我们希望使算法尽可能简单,以便将注意力集中在并发实用程序中。
为了构造反向索引,我们不删除任何单词,也不使用任何词干算法。我们希望使算法尽可能简单,以便将注意力集中在并发工具中。
此处解释的相同原理可用于获取有关文档集合的其他信息,例如,每个文档的向量表示,可作为**聚类算法**的输入,如您将在[第 6 章](06.html#173722-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 6. Optimizing Divide and Conquer Solutions – The Fork/Join Framework")*中了解的优化分而治之的解决方案——Fork/Join 框架*
此处解释的相同原理可用于获取有关文档集合的其他信息,例如,每个文档的向量表示,可作为**聚类算法**的输入,如您将在[第 6 章](06.html#173722-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 6. Optimizing Divide and Conquer Solutions – The Fork/Join Framework")*中了解的优化分的解决方案——Fork/Join 框架*
与其他示例一样,您将实现这些操作的串行和并发版本,以验证并发性在这种情况下对我们有帮助。
......@@ -804,7 +804,7 @@ private static void updateInvertedIndex(Map<String, Integer> voc, Map<String, Li
一个可能的选择是创建其他任务来处理与每个任务相关联的`Future`对象,Java 并发 API 为我们提供了一个优雅的解决方案,用`CompletionService`接口及其实现`ExecutorCompletionService`类来实现这个解决方案。
`CompletionService`对象是一种具有执行器的机制,它允许您将任务的产生与这些任务的结果的消耗解耦。您可以使用`submit()`方法将任务发送给执行者,并在执行者使用`poll()``take()`方法完成任务后获得任务结果。因此,对于我们的解决方案,我们将实以下要素:
`CompletionService`对象是一种具有执行器的机制,它允许您将任务的产生与这些任务的结果的消耗解耦。您可以使用`submit()`方法将任务发送给执行者,并在执行者使用`poll()``take()`方法完成任务后获得任务结果。因此,对于我们的解决方案,我们将实以下要素:
* 用于执行任务的`CompletionService`对象。
* 每个文档的一项任务,用于解析文档并生成其词汇表。此任务将由`CompletionService`对象执行。这些任务在`IndexingTask`类中实现。
......
# 第 5 章。分为阶段的运行任务–阶段器类
# 四、分阶段运行任务——相位器类
并发 API 中最重要的元素是它为程序员提供的同步机制。**同步**是协调两个或多个任务以获得所需结果。当必须按预定义顺序执行两个或多个任务时,您可以同步它们的执行;当一次只有一个线程可以执行代码片段或修改内存块时,您可以同步对共享资源的访问。Java 8 并发 API 提供了许多同步机制,从基本的`synchronized`关键字或`Lock`接口及其实现,以保护关键部分到更高级的`CyclicBarrier``CountDownLatch`类,这些类允许您同步不同任务的执行顺序。在 Java7 中,并发 API 引入了`Phaser`类。此类提供了一个强大的机制(**移相器**来执行分阶段的任务。任务可以要求 Phaser 类等待所有其他参与者完成该阶段。在本章中,我们将介绍以下主题:
......@@ -49,7 +49,7 @@ Java 将此过程称为参与者注册。通常情况下,参与者在执行开
* `phase`:这是已经完成的阶段的编号。第一阶段是数字零
* `registeredParties`:表示参与任务的数量
如果您想在两个阶段之间执行一些代码,例如,排序或转换一些数据,您可以实现自己的阶段器,扩展`Phaser`类并重写此方法。
如果您想在两个阶段之间执行一些代码,例如,排序或转换一些数据,您可以实现自己的相位器,扩展`Phaser`类并重写此方法。
移相器可处于两种状态:
......@@ -129,7 +129,7 @@ public class Word implements Comparable<Word> {
private double tfIdf;
```
我们实了其他感兴趣的方法,如下所示:
我们实了其他感兴趣的方法,如下所示:
* 类的构造函数,它初始化单词(以接收到的单词作为参数)和`df`属性(值为`1`
* `addTf()`方法,增加`tf`属性。
......@@ -201,7 +201,7 @@ public class Document {
}
```
`HashMap`类不是同步的,但我们可以在并发应用程序中使用它,因为它不会在不同的任务之间共享。一个`Document`对象将只由一个任务生成,因此我们在利用`HashMap`类派生的并发版本中不会有竞争条件。
`HashMap`类不是同步的,但我们可以在并发应用中使用它,因为它不会在不同的任务之间共享。一个`Document`对象将只由一个任务生成,因此我们在利用`HashMap`类派生的并发版本中不会有竞争条件。
### DocumentParser 类
......@@ -499,7 +499,7 @@ public class KeywordExtractionTask implements Runnable {
主要任务和其他任务的最后阶段将有所不同。主任务使用`Phaser`类的`arriveAndAwaitAdvance()`方法等待所有任务的第二阶段结束,然后在控制台中写入整个集合中最好的 100 个关键字。最后,它使用`arriveAndDeregister()`方法从相位器注销。
其余任务使用`arriveAndDeregister()`方法标记第二阶段的结束,从阶段器注销,并完成其执行。
其余任务使用`arriveAndDeregister()`方法标记第二阶段的结束,从相位器注销,并完成其执行。
当所有任务完成工作后,所有任务都已从相位器注销。移相器将无任何参与方,并将进入终止状态:
......@@ -752,7 +752,7 @@ public class Individual implements Comparable<Individual> {
}
```
我们还实`compareTo()`方法,使用适应度函数的结果比较两个个体:
我们还实`compareTo()`方法,使用适应度函数的结果比较两个个体:
```java
@Override
......@@ -924,7 +924,7 @@ public class SharedData {
### GeneticPhaser 类
我们需要在任务的阶段变化上执行代码,因此我们必须实现我们自己的阶段器,并覆盖`onAdvance()`方法,该方法在所有各方完成一个阶段后、开始执行下一个阶段之前执行。`GeneticPhaser`类实现了这个移相器。它存储要使用的`SharedData`对象,并将其作为参数接收给构造函数:
我们需要在任务的阶段变化上执行代码,因此我们必须实现我们自己的相位器,并覆盖`onAdvance()`方法,该方法在所有各方完成一个阶段后、开始执行下一个阶段之前执行。`GeneticPhaser`类实现了这个移相器。它存储要使用的`SharedData`对象,并将其作为参数接收给构造函数:
```java
public class GeneticPhaser extends Phaser {
......@@ -1188,7 +1188,7 @@ public class ConcurrentGeneticAlgorithm {
任务可以以不同的方式与相位器同步。最常见的是向移相器指示它已完成一个阶段的执行,并希望使用`arriveAndAwaitAdvance()`方法继续执行下一个阶段。此方法将休眠线程,直到其余任务完成实际阶段。但是,您可以使用其他方法来同步任务。方法`arrive()`用于通知移相器您已完成当前阶段,但您不会等待其余任务(使用此方法时要非常小心)。`arriveAndDeregister()`方法用于通知移相器您已完成当前阶段,并且您不想继续在移相器中工作(通常,因为您已完成工作)。最后,可以使用`awaitAdvance()`方法等待当前阶段的结束。
您可以使用`onAdvance()`方法,在所有任务完成当前阶段后以及开始新阶段之前,控制阶段变化并执行代码。此方法在两个阶段的执行之间调用,并将阶段数和阶段器中的参与者数作为参数接收。您可以扩展`Phaser`类并重写此方法以在两个阶段之间执行代码。
您可以使用`onAdvance()`方法,在所有任务完成当前阶段后以及开始新阶段之前,控制阶段变化并执行代码。此方法在两个阶段的执行之间调用,并将阶段数和相位器中的参与者数作为参数接收。您可以扩展`Phaser`类并重写此方法以在两个阶段之间执行代码。
移相器可以处于两种状态:激活状态(同步任务时)和终止状态(完成工作时)。当所有参与者调用`arriveAndDeregister()`方法或`onAdvance()`方法返回`true`值时(默认情况下总是返回`false`,移相器将进入终止状态。当`Phaser`类处于终止状态时,它将不接受新的参与者,并且同步方法将始终立即返回。
......
# 第 6 章:优化分而治之的解决方案——Fork/Join 框架
# 六、优化分治的解决方案——Fork/Join 框架
[第 2 章](02.html#KVCC1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 2. Managing Lots of Threads – Executors")*管理大量线程–执行器*[第 3 章](03.html#QMFO1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 3. Getting the Maximum from Executors")*从执行器获取最大值*[第 4 章](04.html#VF2I1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 4. Getting Data from the Tasks – The Callable and Future Interfaces")*从任务获取数据–可调用和未来接口*,您学习了如何使用执行器作为一种机制来提高执行大量并发任务的并发应用程序的性能。Java7 并发 API 通过 Fork/Join 框架引入了一种特殊的执行器。该框架旨在为那些可以使用分而治之设计范式解决的问题实现最佳并行解决方案。在本章中,我们将介绍以下主题:
[第 2 章](02.html#KVCC1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 2. Managing Lots of Threads – Executors")*管理大量线程–执行器*[第 3 章](03.html#QMFO1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 3. Getting the Maximum from Executors")*从执行器获取最大值*[第 4 章](04.html#VF2I1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 4. Getting Data from the Tasks – The Callable and Future Interfaces")*从任务获取数据–可调用和未来接口*,您学习了如何使用执行器作为一种机制来提高执行大量并发任务的并发应用的性能。Java7 并发 API 通过 Fork/Join 框架引入了一种特殊的执行器。该框架旨在为那些可以使用分治设计范式解决的问题实现最佳并行解决方案。在本章中,我们将介绍以下主题:
* Fork/Join 框架简介
* 第一个例子是 k-means 聚类算法
......@@ -11,8 +11,8 @@
Java5 中引入的 executor 框架提供了一种执行并发任务的机制,无需创建、启动和完成线程。这个框架使用一个线程池来执行发送给执行者的任务,并将它们用于多个任务。这种机制为程序员提供了一些优势,如下所示:
* 编写并发应用程序更容易,因为您不必担心创建线程。
* 更容易控制执行者和应用程序使用的资源。您可以创建仅使用预定义线程数的执行器。如果发送更多任务,执行器将它们存储在队列中,直到有线程可用。
* 编写并发应用更容易,因为您不必担心创建线程。
* 更容易控制执行者和应用使用的资源。您可以创建仅使用预定义线程数的执行器。如果发送更多任务,执行器将它们存储在队列中,直到有线程可用。
* 执行器减少了线程创建和重用线程所带来的开销。在内部,它管理一个线程池,该线程池重用线程来执行多个任务。
分治算法是一种非常流行的设计技术。要使用此技术解决问题,可以将其划分为更小的问题。你以递归的方式重复这个过程,直到你要解决的问题小到可以直接解决为止。这些类型的问题可以使用 executor 来解决,但是为了更有效地解决它们,Java7 并发 API 引入了 Fork/Join 框架。
......@@ -44,9 +44,9 @@ if ( problem.size() > DEFAULT_SIZE) {
* `fork()`方法:此方法允许您向 Fork/Join 执行器发送子任务
* `join()`方法:此方法允许您等待子任务完成并返回其结果
这些方法有不同的变体,您将在示例中看到。Fork/Join 框架还有另一个关键部分:工作窃取算法,它决定要执行哪些任务。当一个任务正在等待使用`join()`方法完成子任务时,执行该任务的线程从正在等待的任务池中获取另一个任务并开始执行。这样,Fork/Join 执行器的线程总是通过提高应用程序的性能来执行任务。
这些方法有不同的变体,您将在示例中看到。Fork/Join 框架还有另一个关键部分:工作窃取算法,它决定要执行哪些任务。当一个任务正在等待使用`join()`方法完成子任务时,执行该任务的线程从正在等待的任务池中获取另一个任务并开始执行。这样,Fork/Join 执行器的线程总是通过提高应用的性能来执行任务。
Java8 在 Fork/Join 框架中包含了一个新特性。现在,每个 Java 应用程序都有一个名为 common pool 的默认`ForkJoinPool`。您可以通过调用`ForkJoinPool.commonPool()`静态方法获取。您不需要显式地创建一个(尽管您可以)。默认情况下,此默认分叉/联接执行器将使用由计算机的可用处理器确定的线程数。您可以通过更改系统属性`java.util.concurrent.ForkJoinPool.common.parallelism`的值来更改此默认行为。
Java8 在 Fork/Join 框架中包含了一个新特性。现在,每个 Java 应用都有一个名为 common pool 的默认`ForkJoinPool`。您可以通过调用`ForkJoinPool.commonPool()`静态方法获取。您不需要显式地创建一个(尽管您可以)。默认情况下,此默认分叉/联接执行器将使用由计算机的可用处理器确定的线程数。您可以通过更改系统属性`java.util.concurrent.ForkJoinPool.common.parallelism`的值来更改此默认行为。
JavaAPI 的一些特性使用 Fork/Join 框架来实现并发操作。例如,`Arrays`类对数组进行并行排序的`parallelSort()`方法和 Java 8 中引入的并行流(后面将在[第 7 章](07.html#1CQAE2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 7. Processing Massive Datasets with Parallel Streams – The Map and Reduce Model")中介绍,*使用并行流处理海量数据集——Map and Reduce 模型*[第 8 章](08.html#1GKCM1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 8. Processing Massive Datasets with Parallel Streams – The Map and Collect Model")*使用并行流处理海量数据集–映射和收集模型*使用此框架。
......@@ -86,9 +86,9 @@ Fork/Join 框架中有五个基本类:
* 如果您对集群的初始向量进行随机初始化,正如我们前面所建议的,两次执行对同一项目集进行分类可能会得到不同的结果。
* 集群的数量是预先定义的。从分类的角度来看,此属性的错误选择将导致较差的结果。
尽管如此,该算法还是非常流行于对不同种类的项目进行聚类。为了测试我们的算法,您将实现一个应用程序来对一组文档进行集群。作为一个文档集合,我们采用了维基百科页面的简化版本,其中包含了我们在[第 4 章](04.html#VF2I1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 4. Getting Data from the Tasks – The Callable and Future Interfaces")中介绍的电影语料库信息,*从任务中获取数据–可调用和未来接口*。我们只拿了 1000 份文件。为了表示每个文档,我们必须使用向量空间模型表示。通过这种表示,每个文档都表示为一个数字向量,其中向量的每个维度表示一个单词或一个术语,其值是定义该单词或术语在文档中重要性的度量。
尽管如此,该算法还是非常流行于对不同种类的项目进行聚类。为了测试我们的算法,您将实现一个应用来对一组文档进行集群。作为一个文档集合,我们采用了维基百科页面的简化版本,其中包含了我们在[第 4 章](04.html#VF2I1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 4. Getting Data from the Tasks – The Callable and Future Interfaces")中介绍的电影语料库信息,*从任务中获取数据–可调用和未来接口*。我们只拿了 1000 份文件。为了表示每个文档,我们必须使用向量空间模型表示。通过这种表示,每个文档都表示为一个数字向量,其中向量的每个维度表示一个单词或一个术语,其值是定义该单词或术语在文档中重要性的度量。
当使用向量空间模型表示文档集合时,向量的维数将与整个集合中不同单词的数量相同,因此向量将有很多零值,因为每个文档不包含所有单词。您可以在内存中使用更优化的表示,以避免所有这些零值,并节省内存,从而提高应用程序的性能。
当使用向量空间模型表示文档集合时,向量的维数将与整个集合中不同单词的数量相同,因此向量将有很多零值,因为每个文档不包含所有单词。您可以在内存中使用更优化的表示,以避免所有这些零值,并节省内存,从而提高应用的性能。
在我们的案例中,我们选择**术语频率–逆文档频率****tf idf**作为定义每个单词重要性的度量,而 tf idf 较高的 50 个单词作为表示每个文档的术语。
......@@ -258,7 +258,7 @@ public void initialize(Random random) {
## 序列版本
一旦我们描述了应用程序的公共部分,让我们看看如何实现 k-means 聚类算法的串行版本。我们将使用两个类:`SerialKMeans`,实现该算法,以及`SerialMain`,实现`main()`方法来执行该算法。
一旦我们描述了应用的公共部分,让我们看看如何实现 k-means 聚类算法的串行版本。我们将使用两个类:`SerialKMeans`,实现该算法,以及`SerialMain`,实现`main()`方法来执行该算法。
### SerialKMeans 类
......@@ -1389,7 +1389,7 @@ Arrays.parallelSort()
# 总结
而治之设计技术是解决各种问题的一种非常流行的方法。你把原来的问题分成几个小问题,然后把这些问题分成几个小问题,直到我们有足够的简单问题来直接解决它。在版本 7 中,Java 并发 API 引入了一种针对此类问题优化的特殊类型的`Executor`。这是 Fork/Join 框架。它基于以下两个操作:
设计技术是解决各种问题的一种非常流行的方法。你把原来的问题分成几个小问题,然后把这些问题分成几个小问题,直到我们有足够的简单问题来直接解决它。在版本 7 中,Java 并发 API 引入了一种针对此类问题优化的特殊类型的`Executor`。这是 Fork/Join 框架。它基于以下两个操作:
* **fork**:这允许您创建一个新的子任务
* **加入**:这允许您等待子任务的完成并获取其结果
......
# 第 7 章,用并行流处理海量数据集——Map 和 Reduce 模型
# 七、用并行流处理海量数据集——映射和归约模型
毫无疑问,Java8 中引入的最重要的创新是**lambda**表达式和**流**API。流是可以顺序或并行方式处理的元素序列。我们可以应用中间操作转换流,然后执行最终计算以获得所需的结果(列表、数组、数字等)。在本章中,我们将介绍以下主题:
* 溪流导论
* 第一个示例–数字摘要应用程序
* 第一个示例–数字摘要应用
* 第二个示例–信息检索搜索工具
# 溪流简介
......@@ -47,7 +47,7 @@
`Stream`API 提供的其他有趣功能是,您可以生成和流式处理目录或文件的内容。`Files`类提供了使用流处理文件的不同方法。例如,`find()`方法返回一个流,其中包含满足特定条件的文件树中文件的`Path`对象。`list()`方法返回包含目录内容的`Path`对象流。`walk()`方法返回`Path`对象流,使用深度优先算法处理目录树中的所有对象。但是最有趣的方法是`lines()`方法,它创建了一个带有文件行的`String`对象流,因此您可以使用流处理其内容。不幸的是,这里提到的所有方法并行性都很差,除非您有数千个元素(文件或行)。
另外,您可以使用`Stream`接口提供的两种方法`generate()``iterate()`创建流。`generate()`方法接收以对象类型作为参数的`Supplier`参数,并生成该类型对象的无限顺序流。`Supplier`接口采用`get()`方式。每次流需要一个新对象时,它都会调用此方法来获取流的下一个值。正如我们前面提到的,流以惰性的方式处理数据,因此流的无限性没有问题。您将使用其他方法以有限的方式转换该流。`iterate()`方法类似,但在本例中,该方法接收种子和`UnaryOperator`。第一个值是将`UnaryOperator`应用于种子的结果;第二个值是将`UnaryOperator`应用于第一个结果的结果,依此类推。由于并发应用程序的性能,应该尽可能避免使用这种方法。
另外,您可以使用`Stream`接口提供的两种方法`generate()``iterate()`创建流。`generate()`方法接收以对象类型作为参数的`Supplier`参数,并生成该类型对象的无限顺序流。`Supplier`接口采用`get()`方式。每次流需要一个新对象时,它都会调用此方法来获取流的下一个值。正如我们前面提到的,流以惰性的方式处理数据,因此流的无限性没有问题。您将使用其他方法以有限的方式转换该流。`iterate()`方法类似,但在本例中,该方法接收种子和`UnaryOperator`。第一个值是将`UnaryOperator`应用于种子的结果;第二个值是将`UnaryOperator`应用于第一个结果的结果,依此类推。由于并发应用的性能,应该尽可能避免使用这种方法。
还有更多的流源,如下所示:
......@@ -110,7 +110,7 @@ Java8 和 streams 允许程序员实现类似的东西。`Stream`接口定义了
## 并发版本
我们的数值总结应用程序非常简单。它具有以下组件:
我们的数值总结应用非常简单。它具有以下组件:
* `Record`:此类定义了文件中每条记录的内部结构。它定义了每个记录的 21 个属性以及相应的`get()``set()`方法来建立它们的值。它的代码非常简单,因此不会包含在本书中。
* `ConcurrentDataLoader`:此类将加载`bank-additional-full.csv`文件中的数据,并将其转换为`Record`对象列表。我们将使用流来加载数据并进行转换。
......@@ -531,7 +531,7 @@ velankanni:4,18005302.txt:10.13,20681361.txt:10.13,45672176.txt:10 .13,6592085.t
## 第一种方式-全文档查询
在我们的第一种方法中,我们将使用与一个单词相关的所有文档。我们的搜索过程的实步骤如下:
在我们的第一种方法中,我们将使用与一个单词相关的所有文档。我们的搜索过程的实步骤如下:
1. 我们在倒排索引中选择与查询词对应的行。
2. 我们将所有文档列表分组到一个列表中。如果一个文档与两个或多个不同的单词相关联,我们将文档中这些单词的`tfxidf`值相加,以获得文档的最终`tfxidf`值。如果文档仅与一个单词关联,则该单词的`tfxidf`值将是该文档的最终`tfxidf`值。
......@@ -811,7 +811,7 @@ public class ContentMapper implements Function<Document, String> {
## 第四种方法-预加载反向索引
之前的三个解决方案在并行执行时出现问题。如前所述,并行流是使用 Java 并发 API 提供的公共 Fork/Join 池执行的。在[第 6 章](06.html#173722-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 6. Optimizing Divide and Conquer Solutions – The Fork/Join Framework")*优化分而治之的解决方案——Fork/Join 框架*中,您了解到不应将 I/O 操作用作任务内部文件中的读写数据。这是因为当线程阻止从文件读取或写入数据时,框架不会使用工作窃取算法。当我们使用文件作为流的源时,我们正在惩罚我们的并发解决方案。
之前的三个解决方案在并行执行时出现问题。如前所述,并行流是使用 Java 并发 API 提供的公共 Fork/Join 池执行的。在[第 6 章](06.html#173722-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 6. Optimizing Divide and Conquer Solutions – The Fork/Join Framework")*优化分的解决方案——Fork/Join 框架*中,您了解到不应将 I/O 操作用作任务内部文件中的读写数据。这是因为当线程阻止从文件读取或写入数据时,框架不会使用工作窃取算法。当我们使用文件作为流的源时,我们正在惩罚我们的并发解决方案。
这个问题的一个解决方案是将数据读取到数据结构中,然后从该数据结构创建流。显然,当我们与其他方法进行比较时,这种方法的执行时间会更小,但是我们希望比较串行和并发版本,以看到(正如我们所期望的)并发版本比串行版本提供更好的性能。这种方法的缺点是需要将数据结构存储在内存中,因此需要大量内存。
......@@ -1095,7 +1095,7 @@ public class ConcurrentMain {
| 简化搜索 | 3458.351 | 3230.017 |
| HTML 搜索 | 3298.996 | 3298.632 |
| 预加载搜索 | 153.414 | 105.195 |
| 执行搜查 | 154.679 | 102.135 |
| 执行搜查 | 154.679 | 102.135 |
对于第二个查询,单词为`gone``with``the``wind`,这些是以毫秒为单位获得的执行时间:
......@@ -1114,7 +1114,7 @@ public class ConcurrentMain {
| 简化搜索 | 3249.930 | 3260.026 |
| HTML 搜索 | 3299.625 | 3379.277 |
| 预加载搜索 | 154.631 | 113.757 |
| 执行搜查 | 156.091 | 106.418 |
| 执行搜查 | 156.091 | 106.418 |
对于第三个查询,使用单词`rocky`,这些是以毫秒为单位获得的执行时间:
......@@ -1133,7 +1133,7 @@ public class ConcurrentMain {
| 简化搜索 | 3318.343 | 3279.247 |
| HTML 搜索 | 3323.345 | 3333.624 |
| 预加载搜索 | 151.416 | 97.092 |
| 执行搜查 | 155.033 | 103.907 |
| 执行搜查 | 155.033 | 103.907 |
最后,以下是返回反转索引信息的方法的平均执行时间(以毫秒为单位):
......@@ -1180,8 +1180,8 @@ public class ConcurrentMain {
在本章中,我们介绍了 streams,这是 Java8 中引入的一个新特性,其灵感来自函数式编程,并准备使用新的 lambda 表达式。流是一个数据序列(不是数据结构),它允许您以顺序或并发方式应用操作序列,以过滤、转换、排序、减少或组织这些元素以获得最终对象。
您还了解了在顺序或并发应用程序中使用流时必须考虑的流的主要特征。
您还了解了在顺序或并发应用中使用流时必须考虑的流的主要特征。
最后,我们在两个样本中使用了流。在第一个示例中,我们几乎使用了`Stream`接口提供的所有方法来计算大数据集的统计数据。我们使用了 UCI 机器学习库的银行营销数据集及其 45211 条记录。在第二个示例中,我们对反向索引中的搜索应用程序实现了不同的方法,以获取与查询最相关的文档。这是信息检索领域最常见的任务之一。为此,我们使用`reduce()`方法作为流的终端操作。
最后,我们在两个样本中使用了流。在第一个示例中,我们几乎使用了`Stream`接口提供的所有方法来计算大数据集的统计数据。我们使用了 UCI 机器学习库的银行营销数据集及其 45211 条记录。在第二个示例中,我们对反向索引中的搜索应用实现了不同的方法,以获取与查询最相关的文档。这是信息检索领域最常见的任务之一。为此,我们使用`reduce()`方法作为流的终端操作。
在下一章中,我们将继续使用 streams,但更多地关注 `collect()`终端操作。
\ No newline at end of file
# 第 8 章,用并行流处理海量数据集——地图和采集模型
# 八、用并行流处理海量数据集——映射和收集模型
[第 7 章](07.html#1CQAE2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 7. Processing Massive Datasets with Parallel Streams – The Map and Reduce Model")*中,我们引入了流的概念,即 Java 8 的新特性。流是可以并行或顺序处理的元素序列。在本章中,您将学习如何使用具有以下主题的流:*
......@@ -550,7 +550,7 @@ public class ConcurrentLoaderAccumulator implements
## 序列版本
与本书中的其他示例一样,我们实现了该示例的串行版本,以检查并行流是否提高了应用程序的性能。要实现此串行版本,我们必须遵循以下步骤:
与本书中的其他示例一样,我们实现了该示例的串行版本,以检查并行流是否提高了应用的性能。要实现此串行版本,我们必须遵循以下步骤:
*`ConcurrentLinkedDeque`数据结构替换为`List``ArrayList`数据结构
*`parallelStrem()`方法改为`stream()`方法
......@@ -656,7 +656,7 @@ A-B-(B,C,D),(A,C,D,E)
## 基类
与本书中的其他示例一样,我们已经实现了该示例的串行和并发版本,以验证并行流提高了应用程序的性能。两个版本共享一些类。
与本书中的其他示例一样,我们已经实现了该示例的串行和并发版本,以验证并行流提高了应用的性能。两个版本共享一些类。
### 人物类
......
# 第 9 章。深入研究并发数据结构和同步实用程序
# 九、深入研究并发数据结构和同步工具
每个计算机程序中最重要的元素之一是**数据结构**。数据结构允许我们存储应用程序根据需要以不同方式读取、转换和写入的数据。选择合适的数据结构是获得良好性能的关键。错误的选择会大大降低算法的性能。Java 并发 API 包括一些设计用于并发应用程序的数据结构,而不会引起数据不一致或信息丢失。
每个计算机程序中最重要的元素之一是**数据结构**。数据结构允许我们存储应用根据需要以不同方式读取、转换和写入的数据。选择合适的数据结构是获得良好性能的关键。错误的选择会大大降低算法的性能。Java 并发 API 包括一些设计用于并发应用的数据结构,而不会引起数据不一致或信息丢失。
并发应用程序中的另一个关键点是**同步机制**。您可以使用它们通过创建一个关键部分来实现互斥,也就是说,一段代码一次只能由一个线程执行。但您也可以使用同步机制来实现线程之间的依赖关系,例如,当一个并发任务必须等待另一个任务的完成时。Java 并发 API 包括基本的同步机制,比如`synchronized`关键字和非常高级的实用程序,比如您在[第 5 章](05.html#1394Q1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 5. Running Tasks Divided into Phases – The Phaser Class")中使用的`CyclicBarrier`类或`Phaser`类,*分阶段运行任务–阶段器类*
并发应用中的另一个关键点是**同步机制**。您可以使用它们通过创建一个关键部分来实现互斥,也就是说,一段代码一次只能由一个线程执行。但您也可以使用同步机制来实现线程之间的依赖关系,例如,当一个并发任务必须等待另一个任务的完成时。Java 并发 API 包括基本的同步机制,比如`synchronized`关键字和非常高级的工具,比如您在[第 5 章](05.html#1394Q1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 5. Running Tasks Divided into Phases – The Phaser Class")中使用的`CyclicBarrier`类或`Phaser`类,*分阶段运行任务–相位器类*
在本章中,我们将介绍以下主题:
......@@ -13,12 +13,12 @@
每一个计算机程序都处理数据。他们从数据库、文件或其他源获取数据,转换该数据,然后将转换后的数据写入数据库、文件或其他目标。这些程序处理存储在内存中的数据,并使用数据结构将数据存储在内存中。
在实现并发应用程序时,必须非常小心地使用数据结构。如果不同的线程可以修改存储在唯一数据结构中的数据,则必须使用同步机制来保护对该数据结构的修改。如果不这样做,可能会出现数据争用情况。您的应用程序有时可能工作正常,但下一次可能会因随机异常而崩溃、卡在无限循环中或默默地产生错误的结果。结果将取决于执行的顺序。
在实现并发应用时,必须非常小心地使用数据结构。如果不同的线程可以修改存储在唯一数据结构中的数据,则必须使用同步机制来保护对该数据结构的修改。如果不这样做,可能会出现数据争用情况。您的应用有时可能工作正常,但下一次可能会因随机异常而崩溃、卡在无限循环中或默默地产生错误的结果。结果将取决于执行的顺序。
要避免数据争用情况,您可以:
* 使用非同步数据结构,并自行添加同步机制
* 使用 Java 并发 API 提供的数据结构,该结构在内部实现同步机制,并经过优化以用于并发应用程序
* 使用 Java 并发 API 提供的数据结构,该结构在内部实现同步机制,并经过优化以用于并发应用
第二个选项是最推荐的。通过本节的页面,您将回顾最重要的并发数据结构,这些数据结构特别重视 Java8 的新特性。
......@@ -31,12 +31,12 @@ Java 并发 API 提供两种并发数据结构:
有时,对于阻塞数据结构,我们有一个非阻塞等价物。例如,`ConcurrentLinkedDeque`类是非阻塞数据结构,`LinkedBlockingDeque`是阻塞等价物。阻塞数据结构具有类似于非阻塞数据结构的方法。例如,`Deque`接口定义了`pollFirst()`方法,该方法在 deque 为空时不阻塞返回`null`。每个阻塞队列实现也实现了这个方法。
**Java 集合框架****JCF**提供了一组不同的数据结构,可以用于顺序编程。Java 并发 API 扩展了这些结构,提供了可在并发应用程序中使用的其他结构。这包括:
**Java 集合框架****JCF**提供了一组不同的数据结构,可以用于顺序编程。Java 并发 API 扩展了这些结构,提供了可在并发应用中使用的其他结构。这包括:
* **接口**:扩展了 JCF 提供的接口,增加了一些可以在并发应用中使用的方法
* **类**:实现前面的接口,提供可以在应用中使用的实现
在以下部分中,我们将介绍可在并发应用程序中使用的接口和类。
在以下部分中,我们将介绍可在并发应用中使用的接口和类。
### 接口
......@@ -169,7 +169,7 @@ Java 并发 API 提供了前面描述的接口的不同实现。其中一些没
#### ConcurrentLinkedQueue
此类实现了`Queue`接口以提供线程保存无限队列。在内部,它使用非阻塞算法来保证应用程序中不会出现数据竞争。
此类实现了`Queue`接口以提供线程保存无限队列。在内部,它使用非阻塞算法来保证应用中不会出现数据竞争。
#### 链接锁紧装置
......@@ -210,7 +210,7 @@ Java 并发 API 提供了前面描述的接口的不同实现。其中一些没
### ConcurrentHashMap 的第一个示例
[第 8 章](08.html#1GKCM1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 8. Processing Massive Datasets with Parallel Streams – The Map and Collect Model")*使用并行流处理海量数据集–地图和收集模型*中,您实现了一个应用程序,用于搜索来自 20000 个亚马逊产品的数据集。我们从亚马逊产品联合采购网络元数据中获取了这些信息,其中包括 548552 种产品的信息,包括 title、salesrank 和类似产品。您可以从[下载此数据集 https://snap.stanford.edu/data/amazon-meta.html](https://snap.stanford.edu/data/amazon-meta.html) 。在该示例中,您使用名为`productsByBuyer``ConcurrentHashMap<String, List<ExtendedProduct>>`来存储关于用户购买的产品的信息。此映射的键是用户的标识符,值是用户购买的产品列表。您将使用该映射来学习如何使用`ConcurrentHashMap`类的新方法。
[第 8 章](08.html#1GKCM1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 8. Processing Massive Datasets with Parallel Streams – The Map and Collect Model")*使用并行流处理海量数据集–地图和收集模型*中,您实现了一个应用,用于搜索来自 20000 个亚马逊产品的数据集。我们从亚马逊产品联合采购网络元数据中获取了这些信息,其中包括 548552 种产品的信息,包括 title、salesrank 和类似产品。您可以从[下载此数据集 https://snap.stanford.edu/data/amazon-meta.html](https://snap.stanford.edu/data/amazon-meta.html) 。在该示例中,您使用名为`productsByBuyer``ConcurrentHashMap<String, List<ExtendedProduct>>`来存储关于用户购买的产品的信息。此映射的键是用户的标识符,值是用户购买的产品列表。您将使用该映射来学习如何使用`ConcurrentHashMap`类的新方法。
#### forEach()方法
......@@ -235,7 +235,7 @@ Java 并发 API 提供了前面描述的接口的不同实现。其中一些没
此方法的其他版本如下所示:
* `forEach(parallelismThreshold, action)`:这是您必须在并发应用程序中使用的方法的版本。如果映射包含的元素多于第一个参数中指定的数量,则此方法将并行执行。
* `forEach(parallelismThreshold, action)`:这是您必须在并发应用中使用的方法的版本。如果映射包含的元素多于第一个参数中指定的数量,则此方法将并行执行。
* `forEachEntry(parallelismThreshold, action)`:与前面相同,但在本例中,该动作是`Consumer`接口的一个实现,该接口接收一个带有键和元素值的`Map.Entry`对象。在这种情况下,还可以使用 lambda 表达式。
* `forEachKey(parallelismThreshold, action)`:与前面相同,但在这种情况下,该动作将仅应用于`ConcurrentHashMap`的键。
* `forEachValue(parallelismThreshold, action)`:与前面相同,但在这种情况下,该动作将仅应用于`ConcurrentHashMap`的值。
......@@ -514,7 +514,7 @@ Java 1.5 中引入了原子变量,以提供对`integer`、`long`、`boolean`
# 同步机制
任务同步是指在这些任务之间进行协调,以获得所需的结果。在并发应用程序中,我们可以有两种同步:
任务同步是指在这些任务之间进行协调,以获得所需的结果。在并发应用中,我们可以有两种同步:
* **进程同步**:当我们想要控制任务的执行顺序时,我们使用这种同步。例如,任务在开始执行之前必须等待其他任务的完成。
* **数据同步**:当两个或多个任务访问同一个内存对象时,我们使用这种同步。在这种情况下,必须保护对该对象的写入操作中的访问。如果不这样做,可能会出现一个数据竞争条件,即程序的最终结果会随着执行的不同而变化。
......@@ -527,7 +527,7 @@ Java 还提供其他同步机制:
* 实现*Edsger Dijkstra*引入的众所周知的**信号量**同步机制的`Semaphore`类。
* `CountDownLatch`允许您实现一个或多个线程等待其他线程完成的情况。
* `CyclicBarrier`允许您在一个公共点上同步不同的任务。
* `Phaser`允许您分阶段执行并发任务。我们在[第 5 章](05.html#1394Q1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 5. Running Tasks Divided into Phases – The Phaser Class")中对该机制进行了详细描述,*将运行任务分为阶段–阶段器类*
* `Phaser`允许您分阶段执行并发任务。我们在[第 5 章](05.html#1394Q1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 5. Running Tasks Divided into Phases – The Phaser Class")中对该机制进行了详细描述,*将运行任务分为阶段–相位器类*
* `Exchanger`允许您在两个任务之间实现一个数据交换点。
* Java 8 的新特性`CompletableFuture`扩展了执行者任务的`Future`机制,以异步方式生成任务的结果。您可以指定生成结果后要执行的任务,以便控制任务的执行顺序。
......@@ -1041,7 +1041,7 @@ public class CompletableMain {
# 总结
在本章中,我们回顾了所有并发应用程序的两个组件。第一个是数据结构。每个程序都使用它们在内存中存储它必须处理的信息。我们很快就了解了并发数据结构,详细描述了 Java 8 并发 API 中引入的影响`ConcurrentHashMap`类和实现`Collection`接口的类的新特性。
在本章中,我们回顾了所有并发应用的两个组件。第一个是数据结构。每个程序都使用它们在内存中存储它必须处理的信息。我们很快就了解了并发数据结构,详细描述了 Java 8 并发 API 中引入的影响`ConcurrentHashMap`类和实现`Collection`接口的类的新特性。
第二种是同步机制,允许您在多个并发任务需要修改数据时保护数据,并在必要时控制任务的执行顺序。在本例中,我们还很快地介绍了同步机制,详细描述了`CompletableFuture`,这是 Java 8 并发 API 的一个新特性。
......
# 第 10 章:片段整合和备选方案实施
# 十、片段整合和备选方案实现
[第 2 章](02.html#KVCC1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 2. Managing Lots of Threads – Executors")*管理大量线程–执行器*,到[第 8 章](08.html#1GKCM1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 8. Processing Massive Datasets with Parallel Streams – The Map and Collect Model")*使用并行流处理海量数据集–映射和收集模型*,您使用 Java 并发 API 的最重要部分实现了不同的示例。通常,这些示例是真实的,但大多数情况下,这些示例可能是更大系统的一部分。例如,在[第 4 章](04.html#VF2I1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 4. Getting Data from the Tasks – The Callable and Future Interfaces")*从任务获取数据–可调用和未来接口*中,您实现了一个应用程序来构建一个反向索引,用于信息检索系统。在[第 6 章](06.html#173722-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 6. Optimizing Divide and Conquer Solutions – The Fork/Join Framework")*优化分而治之的解决方案——Fork/Join 框架*中,您实现了 k-means 聚类算法对一组文档进行聚类。但是,您可以实现一个完整的信息检索应用程序,该应用程序读取一组文档,使用向量空间模型表示它们,并使用 K-NN 算法对它们进行聚类。在这些情况下,您有可能使用不同并发技术的不同部分(执行器、流等),但它们必须在它们之间进行同步和通信,以获得所需的结果。
[第 2 章](02.html#KVCC1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 2. Managing Lots of Threads – Executors")*管理大量线程–执行器*,到[第 8 章](08.html#1GKCM1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 8. Processing Massive Datasets with Parallel Streams – The Map and Collect Model")*使用并行流处理海量数据集–映射和收集模型*,您使用 Java 并发 API 的最重要部分实现了不同的示例。通常,这些示例是真实的,但大多数情况下,这些示例可能是更大系统的一部分。例如,在[第 4 章](04.html#VF2I1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 4. Getting Data from the Tasks – The Callable and Future Interfaces")*从任务获取数据–可调用和未来接口*中,您实现了一个应用来构建一个反向索引,用于信息检索系统。在[第 6 章](06.html#173722-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 6. Optimizing Divide and Conquer Solutions – The Fork/Join Framework")*优化分治的解决方案——Fork/Join 框架*中,您实现了 k-means 聚类算法对一组文档进行聚类。但是,您可以实现一个完整的信息检索应用,该应用读取一组文档,使用向量空间模型表示它们,并使用 K-NN 算法对它们进行聚类。在这些情况下,您有可能使用不同并发技术的不同部分(执行器、流等),但它们必须在它们之间进行同步和通信,以获得所需的结果。
此外,本书中介绍的所有示例都可以使用 Java 并发 API 的其他组件实现。我们也将讨论其中一些备选方案。
在本章中,我们将介绍以下主题:
* 大数据块同步机制
* 文档集群应用程序的一个示例
*备选方案
* 文档集群应用的一个示例
*备选方案
# 大数据块同步机制
大型计算机应用程序由不同的组件组成,这些组件共同工作以获得所需的功能。这些组件必须在它们之间进行同步和通信。在[第 9 章](09.html#1LCVG2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 9. Diving into Concurrent Data Structures and Synchronization Utilities")*深入到并发数据结构和同步实用程序*中,您了解到可以使用不同的 Java 类来同步任务并在它们之间进行通信。但是,如果要同步的组件也是可以使用不同机制实现其并发性的并发系统,则此任务组织会更加复杂。对于示例,您在应用程序中有一个组件,该组件使用 Fork/Join 框架生成结果,这些结果由使用`Phaser`类同步的其他任务使用。
大型计算机应用由不同的组件组成,这些组件共同工作以获得所需的功能。这些组件必须在它们之间进行同步和通信。在[第 9 章](09.html#1LCVG2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 9. Diving into Concurrent Data Structures and Synchronization Utilities")*深入到并发数据结构和同步工具*中,您了解到可以使用不同的 Java 类来同步任务并在它们之间进行通信。但是,如果要同步的组件也是可以使用不同机制实现其并发性的并发系统,则此任务组织会更加复杂。对于示例,您在应用中有一个组件,该组件使用 Fork/Join 框架生成结果,这些结果由使用`Phaser`类同步的其他任务使用。
在这些情况下,您可以使用以下两种机制来同步和通信这些组件:
......@@ -21,11 +21,11 @@
* **同步**:在这种情况下,发送消息的类等待,直到接收方处理完其消息
* **异步**:在这种情况下,发送消息的类不会等待处理其消息的接收方
在本节中,您将实现一个应用程序来对文档进行集群,该应用程序由四个子系统组成,它们之间通过通信和同步来对文档进行集群。
在本节中,您将实现一个应用来对文档进行集群,该应用由四个子系统组成,它们之间通过通信和同步来对文档进行集群。
# 文档集群应用程序示例
# 文档集群应用示例
此应用程序将读取一组文档,并使用 k-均值聚类算法对其进行组织。为此,我们将使用四个组件:
此应用将读取一组文档,并使用 k-均值聚类算法对其进行组织。为此,我们将使用四个组件:
* **读卡器系统**:该系统将读取所有文档,并将每个文档转换为`String`对象列表。
* **索引器系统**:该系统将处理文档并将其转换为单词列表。同时,它将生成文档集的全局词汇表,其中包含出现在文档集上的所有单词。
......@@ -245,7 +245,7 @@ public class Mapper implements Runnable {
### 集群系统
本系统实现了 k-均值聚类算法。您可以使用[第 5 章](05.html#1394Q1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 5. Running Tasks Divided into Phases – The Phaser Class")*中介绍的元素,将运行任务分为阶段–阶段器类*来实现此系统。这一实施具有以下要素:
本系统实现了 k-均值聚类算法。您可以使用[第 5 章](05.html#1394Q1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 5. Running Tasks Divided into Phases – The Phaser Class")*中介绍的元素,将运行任务分为阶段–相位器类*来实现此系统。这一实现具有以下要素:
* **DistanceMeasurer 类**:该类利用文档信息和簇的质心计算一组`Attribute`对象之间的欧氏距离
* **DocumentCluster 类**:存储关于集群的信息:该集群的质心和文档
......@@ -278,7 +278,7 @@ public class Mapper implements Runnable {
## 文档聚类应用的主类
一旦我们实现了应用程序中使用的所有元素,我们就必须实现系统的`main()`方法。在这种情况下,此方法至关重要,因为它负责启动系统并创建同步系统所需的元素。`Reader``Indexer`系统将同时执行。他们将使用缓冲区在他们之间共享信息。当读卡器读取一个文档时,它将写入缓冲区中的`String`对象列表,然后继续处理下一个文档。它不会等待处理`List`的任务。这是**异步消息传递**的一个示例。`Indexer`系统将从缓冲区中取出文档,对其进行处理,并生成包含文档所有单词的`Vocabulary`对象。`Indexer`系统执行的所有任务共享`Vocabulary`类的实例。这是**共享内存**的一个示例。
一旦我们实现了应用中使用的所有元素,我们就必须实现系统的`main()`方法。在这种情况下,此方法至关重要,因为它负责启动系统并创建同步系统所需的元素。`Reader``Indexer`系统将同时执行。他们将使用缓冲区在他们之间共享信息。当读卡器读取一个文档时,它将写入缓冲区中的`String`对象列表,然后继续处理下一个文档。它不会等待处理`List`的任务。这是**异步消息传递**的一个示例。`Indexer`系统将从缓冲区中取出文档,对其进行处理,并生成包含文档所有单词的`Vocabulary`对象。`Indexer`系统执行的所有任务共享`Vocabulary`类的实例。这是**共享内存**的一个示例。
主类将使用`CountDownLatch`对象的`await()`方法以同步方式等待`Reader``Indexer`系统的终结。此方法阻止调用线程的执行,直到其内部计数器到达 0。
......@@ -381,9 +381,9 @@ public class ClusteringDocs {
`readFileNames()`方法接收一个字符串作为参数,该字符串必须是存储文档集合的目录的路径,并使用该目录中包含的文件名生成一个`ConcurrentLinkedDeque`类的`String`对象。
## 测试我们的文档聚类应用程序
## 测试我们的文档聚类应用
为了测试这个应用程序,我们使用了 100673 个文档中的 10052 个文档的子集,其中包含从维基百科获取的电影信息,作为文档集合。在下图中,您可以看到从执行开始到索引器执行结束的第一部分执行的结果:
为了测试这个应用,我们使用了 100673 个文档中的 10052 个文档的子集,其中包含从维基百科获取的电影信息,作为文档集合。在下图中,您可以看到从执行开始到索引器执行结束的第一部分执行的结果:
![Testing our document clustering application](img/00033.jpeg)
......@@ -423,7 +423,7 @@ public class ClusteringDocs {
## 一种遗传算法
您已经在[第 5 章](05.html#1394Q1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 5. Running Tasks Divided into Phases – The Phaser Class")中实现了这个示例,*运行分为阶段的任务–阶段器类***遗传算法**是一种基于自然选择原则的自适应启发式搜索算法,用于生成**优化****搜索问题**的良好解决方案。对于遗传算法,使用多线程有不同的方法。最经典的是创造*岛屿*。每个线程代表一个岛屿,其中一部分种群进化。有时,岛屿之间的迁移发生在将一些个体从一个岛屿转移到另一个岛屿的过程中。算法完成后,选择所有岛屿上的最佳物种。这种方法大大减少了争用,因为线程很少相互通信。
您已经在[第 5 章](05.html#1394Q1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 5. Running Tasks Divided into Phases – The Phaser Class")中实现了这个示例,*运行分为阶段的任务–相位器类***遗传算法**是一种基于自然选择原则的自适应启发式搜索算法,用于生成**优化****搜索问题**的良好解决方案。对于遗传算法,使用多线程有不同的方法。最经典的是创造*岛屿*。每个线程代表一个岛屿,其中一部分种群进化。有时,岛屿之间的迁移发生在将一些个体从一个岛屿转移到另一个岛屿的过程中。算法完成后,选择所有岛屿上的最佳物种。这种方法大大减少了争用,因为线程很少相互通信。
还有许多出版物和网站中详细描述的其他方法。例如,这个讲义集在[中非常好地总结了这些方法 https://cw.fel.cvut.cz/wiki/_media/courses/a0m33eoa/prednasky/08pgas-handouts.pdf](https://cw.fel.cvut.cz/wiki/_media/courses/a0m33eoa/prednasky/08pgas-handouts.pdf)
......@@ -435,7 +435,7 @@ public class ClusteringDocs {
## 一种关键词提取算法
您已经在[第 5 章](05.html#1394Q1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 5. Running Tasks Divided into Phases – The Phaser Class")中实现了这个示例,*运行分为阶段的任务–阶段器类*。我们使用这种算法来提取描述文档的一小部分单词。我们试图通过 Tf Idf 等方法找到信息量最大的单词。您还可以使用并发 API 的以下组件实现此示例:
您已经在[第 5 章](05.html#1394Q1-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 5. Running Tasks Divided into Phases – The Phaser Class")中实现了这个示例,*运行分为阶段的任务–相位器类*。我们使用这种算法来提取描述文档的一小部分单词。我们试图通过 Tf Idf 等方法找到信息量最大的单词。您还可以使用并发 API 的以下组件实现此示例:
* **线程**:您需要两种线程。第一组的线程将处理文档集,以获取每个单词的文档频率。您需要一个存储集合词汇表的共享数据结构。第二组的线程将再次处理文档,以获取每个文档的关键字,并更新维护整个关键字列表的结构。
* **Fork/Join 框架**:主要思想与上一版本类似。你需要两种任务。第一个获得全球词汇表的文件集合。每个任务将计算文档子集的词汇表。如果子集太大,任务将执行两个子任务。加入子任务后,它会将获得的两个词汇组合成一个。第二组任务将计算关键字列表。每个任务将计算文档子集的关键字列表。如果该子集太大,它将执行两个子任务。当这些任务完成时,父任务将生成一个关键字列表,其中包含子任务返回的列表。
......@@ -469,7 +469,7 @@ public class ClusteringDocs {
您已经在[第 7 章](07.html#1CQAE2-2fff3d3b99304faa8fa9b27f1b5053ba "Chapter 7. Processing Massive Datasets with Parallel Streams – The Map and Reduce Model")*使用并行流处理海量数据集的过程中实现了这个示例—映射和简化模型*。这种算法希望获得关于一组非常大的数据的统计信息。您还可以使用并发 API 的以下组件实现此示例:
* **线程**:我们将有一个对象来存储线程生成的数据。每个线程将处理数据的一个子集,并将该数据的结果存储在公共对象中。也许,我们将不得不对该对象进行后处理以生成最终结果。
* **执行**。这与前一个类似,但在执行器中执行并发任务。
* **执行**。这与前一个类似,但在执行器中执行并发任务。
* **Fork/Join 框架**:与前一个类似,但每个任务都将反向索引的部分划分为更小的块进行处理,直到它们足够小为止。
## 一种无索引的搜索算法
......@@ -492,8 +492,8 @@ public class ClusteringDocs {
在本书中,您实现了许多实际示例。其中一些示例可以作为更大系统的一部分使用。这些较大的系统通常具有不同的并发部分,这些部分必须共享信息并在它们之间进行同步。为了实现同步,我们可以使用三种机制:共享内存,当两个或多个任务共享一个对象或数据结构时,异步消息传递,当一个任务向另一个任务发送消息而不等待其处理时,以及同步消息传递,当一个任务向另一个任务发送消息并等待其处理时。
在本章中,我们实现了一个由四个子系统组成的集群文档应用程序。我们使用前面介绍的机制在这四个子系统之间同步和共享信息。
在本章中,我们实现了一个由四个子系统组成的集群文档应用。我们使用前面介绍的机制在这四个子系统之间同步和共享信息。
我们还修改了书中介绍的一些示例,以讨论其实现的其他替代方案。
在下一章中,您将学习如何获取并发 API 组件的调试信息,以及如何监视和测试并发应用程序。
\ No newline at end of file
在下一章中,您将学习如何获取并发 API 组件的调试信息,以及如何监视和测试并发应用。
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册