提交 a089841d 编写于 作者: W wizardforcel

2021-10-11 21:52:16

上级 509fd8e1
# 序言
# 零、序言
当你使用计算机时,你可以同时做几件事。在文字处理器中编辑文档和阅读电子邮件时,您可以听到音乐。这是因为您的操作系统允许任务的并发性。并发编程是关于平台提供的元素和机制,它使多个任务或程序同时运行,并相互通信以交换数据或相互同步。Java 是一个并发平台,提供了许多类来执行 Java 程序中的并发任务。在每个版本中,Java 都增加了为程序员提供的功能,以促进并发程序的开发。本书介绍了 Java 并发 API 版本 7 中包含的最重要和最有用的机制,因此您可以在应用程序中直接使用它们,如下所示:
当你使用计算机时,你可以同时做几件事。在文字处理器中编辑文档和阅读电子邮件时,您可以听到音乐。这是因为您的操作系统允许任务的并发性。并发编程是关于平台提供的元素和机制,它使多个任务或程序同时运行,并相互通信以交换数据或相互同步。Java 是一个并发平台,提供了许多类来执行 Java 程序中的并发任务。在每个版本中,Java 都增加了为程序员提供的功能,以促进并发程序的开发。本书介绍了 Java 并发 API 版本 7 中包含的最重要和最有用的机制,因此您可以在应用中直接使用它们,如下所示:
* 基本线程管理
* 线程同步机制
* 与执行者的线程创建和管理委派
* Fork/Join 框架可增强应用程序的性能
* Fork/Join 框架可增强应用的性能
* 并发程序的数据结构
* 根据您的需要调整某些并发类的默认行为
* 测试 Java 并发应用程序
* 测试 Java 并发应用
# 这本书涵盖的内容
......@@ -16,7 +16,7 @@
[第 2 章](2.html "Chapter 2. Basic Thread Synchronization")*基本线程同步*将教读者使用底层 Java 机制同步代码。锁和`synchronized`关键字将被详细解释。
[第三章](3.html "Chapter 3. Thread Synchronization Utilities")*线程同步实用程序*将教读者使用 Java 的高级实用程序来管理 Java 中线程之间的同步。它包括如何使用新的 Java7`Phaser`类来同步分阶段的任务的说明。
[第三章](3.html "Chapter 3. Thread Synchronization Utilities")*线程同步工具*将教读者使用 Java 的高级工具来管理 Java 中线程之间的同步。它包括如何使用新的 Java7`Phaser`类来同步分阶段的任务的说明。
[第 4 章](4.html "Chapter 4. Thread Executors")*线程执行器*将教导读者将线程管理委托给执行器。它们允许运行、管理和获得并发任务的结果。
......@@ -26,7 +26,7 @@
[第 7 章](7.html "Chapter 7. Customizing Concurrency Classes")*定制并发类*将教读者如何根据自己的需要调整 Java 并发 API 中一些最有用的类。
[第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用程序*将教读者如何获取 Java 7 并发 API 中一些最有用结构的状态信息。读者还将学习如何使用一些免费工具调试并发应用程序,如 Eclipse、NetBeans IDE 或 FindBugs 应用程序,以检测其应用程序上可能存在的错误。
[第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用*将教读者如何获取 Java 7 并发 API 中一些最有用结构的状态信息。读者还将学习如何使用一些免费工具调试并发应用,如 Eclipse、NetBeans IDE 或 FindBugs 应用,以检测其应用上可能存在的错误。
*第 9 章**附加信息*未出现在本书中,但可从以下链接免费下载:[http://www.packtpub.com/sites/default/files/downloads/Additional](http://www.packtpub.com/sites/default/files/downloads/Additional)
......@@ -34,7 +34,7 @@
*附录**并行编程设计*未出现在本书中,但可从以下链接免费下载:[http://www.packtpub.com/sites/default/files/downloads/Concurrent](http://www.packtpub.com/sites/default/files/downloads/Concurrent)
本附录将向读者介绍一些程序员在开发并发应用程序时应考虑的一些技巧。
本附录将向读者介绍一些程序员在开发并发应用时应考虑的一些技巧。
# 这本书你需要什么
......
# 第一章线程管理
# 一、线程管理
在本章中,我们将介绍:
......@@ -21,13 +21,13 @@
所有现代操作系统都允许执行并发任务。你可以一边听音乐,一边阅读网页上的新闻,一边阅读电子邮件。可以说这种并发是一种**进程级**并发。但在一个过程中,我们也可以同时执行各种任务。在进程内运行的并发任务称为**线程**
与并发相关的另一个概念是**并行**。并发概念有不同的定义和关系。一些作者谈到在单核处理器中使用多个线程执行应用程序时的并发性,因此您可以同时看到程序的执行情况。此外,当您在多核处理器或具有多个处理器的计算机中使用多个线程执行应用程序时,您还可以讨论并行性。其他作者讨论了应用程序线程在没有预定义顺序的情况下执行时的并发性,以及使用各种线程简化问题解决方案时的并行性,其中所有这些线程都是按顺序执行的。
与并发相关的另一个概念是**并行**。并发概念有不同的定义和关系。一些作者谈到在单核处理器中使用多个线程执行应用时的并发性,因此您可以同时看到程序的执行情况。此外,当您在多核处理器或具有多个处理器的计算机中使用多个线程执行应用时,您还可以讨论并行性。其他作者讨论了应用线程在没有预定义顺序的情况下执行时的并发性,以及使用各种线程简化问题解决方案时的并行性,其中所有这些线程都是按顺序执行的。
本章介绍了一些说明如何使用 Java7API 对线程执行基本操作的方法。您将看到如何在 Java 程序中创建和运行线程,如何控制线程的执行,以及如何将一些线程分组以作为一个单元进行操作。
# 创建并运行线程
在这个配方中,我们将学习如何在 Java 应用程序中创建和运行线程。与 Java 语言中的每个元素一样,线程是**对象**。我们有两种在 Java 中创建线程的方法:
在这个配方中,我们将学习如何在 Java 应用中创建和运行线程。与 Java 语言中的每个元素一样,线程是**对象**。我们有两种在 Java 中创建线程的方法:
* 扩展`Thread`类并重写`run()`方法
* 构建一个实现`Runnable`接口的类,然后创建一个`Thread`类的对象,将`Runnable`对象作为参数传递
......@@ -68,7 +68,7 @@
}
```
4. 现在,实现应用程序的主类。创建一个名为`Main`的类,该类包含`main()`方法。
4. 现在,实现应用的主类。创建一个名为`Main`的类,该类包含`main()`方法。
```java
public class Main {
......@@ -910,7 +910,7 @@ Java 中有两种类型的异常:
## 还有更多。。。
`Thread`类还有另一个与未捕获异常处理相关的方法。静态方法`setDefaultUncaughtExceptionHandler()`为应用程序中的所有`Thread`对象建立异常处理程序。
`Thread`类还有另一个与未捕获异常处理相关的方法。静态方法`setDefaultUncaughtExceptionHandler()`为应用中的所有`Thread`对象建立异常处理程序。
当在`Thread`中抛出未捕获的异常时,JVM 会为该异常寻找三个可能的处理程序。
......@@ -924,7 +924,7 @@ Java 中有两种类型的异常:
# 使用局部线程变量
并发应用程序最关键的方面之一是共享数据。这在扩展`Thread`类或实现`Runnable`接口的对象中特别重要。
并发应用最关键的方面之一是共享数据。这在扩展`Thread`类或实现`Runnable`接口的对象中特别重要。
如果您创建实现`Runnable`接口的类的对象,然后使用相同的`Runnable`对象启动各种`Thread`对象,则所有线程共享相同的属性。这意味着,如果更改线程中的属性,所有线程都将受到此更改的影响。
......@@ -963,7 +963,7 @@ Java 中有两种类型的异常:
}
```
3. 现在,让我们实现这个有问题的应用程序的主类。使用`main()`方法创建一个名为`Main`的类。此方法将创建一个`UnsafeTask`类的对象,并使用该对象启动三个线程,每个线程之间休眠 2 秒。
3. 现在,让我们实现这个有问题的应用的主类。使用`main()`方法创建一个名为`Main`的类。此方法将创建一个`UnsafeTask`类的对象,并使用该对象启动三个线程,每个线程之间休眠 2 秒。
```java
public class Core {
......@@ -1190,7 +1190,7 @@ Java 提供了`ThreadGroup`类来处理线程组。一个`ThreadGroup`对象可
# 在一组线程中处理非受控异常
在每种编程语言中,一个非常重要的方面是在应用程序中提供错误情况管理的机制。与几乎所有现代编程语言一样,Java 语言实现了一种基于异常的机制来管理错误情况。它提供了许多类来表示不同的错误。当检测到错误情况时,Java 类会抛出这些异常。您还可以使用这些异常或实现自己的异常来管理类中产生的错误。
在每种编程语言中,一个非常重要的方面是在应用中提供错误情况管理的机制。与几乎所有现代编程语言一样,Java 语言实现了一种基于异常的机制来管理错误情况。它提供了许多类来表示不同的错误。当检测到错误情况时,Java 类会抛出这些异常。您还可以使用这些异常或实现自己的异常来管理类中产生的错误。
Java 还提供了捕获和处理这些异常的机制。有些异常必须使用方法的`throws`子句捕获或重新抛出。这些异常称为检查异常。有些异常不必指定或捕获。这些是未经检查的例外情况。
......@@ -1307,7 +1307,7 @@ Java 还提供了捕获和处理这些异常的机制。有些异常必须使用
* 为有限的资源限制对象的创建很容易。例如,我们只能有一个类型的*n*对象。
* 生成有关对象创建的统计数据很容易。
Java 提供了一个接口,`ThreadFactory`接口来实现`Thread`对象工厂。Java 并发 API 的一些高级实用程序使用线程工厂来创建线程。
Java 提供了一个接口,`ThreadFactory`接口来实现`Thread`对象工厂。Java 并发 API 的一些高级工具使用线程工厂来创建线程。
在这个配方中,我们将学习如何实现一个`ThreadFactory`接口来创建具有个性化名称的`Thread`对象,同时保存创建的`Thread`对象的统计信息。
......
# 第二章线程基本同步
# 二、线程基本同步
在本章中,我们将介绍:
......@@ -12,7 +12,7 @@
# 导言
并发编程中最常见的情况之一是多个执行线程共享一个资源。在并发应用程序中,多个线程读取或写入相同的数据或访问相同的文件或数据库连接是正常的。这些共享资源可能引发错误情况或数据不一致,我们必须实现避免这些错误的机制。
并发编程中最常见的情况之一是多个执行线程共享一个资源。在并发应用中,多个线程读取或写入相同的数据或访问相同的文件或数据库连接是正常的。这些共享资源可能引发错误情况或数据不一致,我们必须实现避免这些错误的机制。
这些问题的解决方案来自于**临界截面**的概念。关键部分是访问共享资源的代码块,不能由多个线程同时执行。
......@@ -144,7 +144,7 @@
}
```
11. 通过创建一个名为`Main`的类来实现应用程序的主类,该类包含`main()`方法。
11. 通过创建一个名为`Main`的类来实现应用的主类,该类包含`main()`方法。
```java
public class Main {
......@@ -196,7 +196,7 @@
## 它是如何工作的。。。
在这个配方中,您已经开发了一个应用程序,可以对模拟银行账户的类的余额进行增减。程序对`addAmount()`方法进行`100`调用,该方法在每次调用中将余额增加`1000`,并对`subtractAmount()`方法进行`100`调用,该方法在每次调用中将余额减少`1000`。您应该期望最终余额和初始余额相等。
在这个配方中,您已经开发了一个应用,可以对模拟银行账户的类的余额进行增减。程序对`addAmount()`方法进行`100`调用,该方法在每次调用中将余额增加`1000`,并对`subtractAmount()`方法进行`100`调用,该方法在每次调用中将余额减少`1000`。您应该期望最终余额和初始余额相等。
您试图使用名为`tmp`的变量来存储帐户余额的值,从而强制出现错误情况,因此您读取帐户余额,增加时间变量的值,然后再次建立帐户余额的值。此外,您使用`Thread`类的`sleep()`方法引入了一点延迟,将执行该方法的线程休眠 10 毫秒,因此如果另一个线程执行该方法,它可以修改引发错误的帐户余额。是`synchronized`关键字机制避免了这些错误。
......@@ -212,17 +212,17 @@
![How it works...](img/7881_02_02.jpg)
使用`synchronized`关键字,我们保证在并发应用程序中正确访问共享数据。
使用`synchronized`关键字,我们保证在并发应用中正确访问共享数据。
正如我们在这个配方的介绍中提到的,只有线程才能访问在声明中使用`synchronized`关键字的对象的方法。如果一个线程(a)正在执行一个`synchronized`方法,而另一个线程(B)想要执行同一对象的其他`synchronized`方法,它将被阻塞,直到线程(a)结束。但如果 threadB 可以访问同一类的不同对象,则不会阻止任何对象。
## 还有更多。。。
`synchronized`关键字会影响应用程序的性能,因此您只能在并发环境中修改共享数据的方法上使用它。如果有多个线程调用`synchronized`方法,则一次只有一个线程执行它们,而其他线程将等待。如果操作不使用`synchronized`关键字,则所有线程可以同时执行该操作,从而减少总执行时间。如果您知道一个方法不会被多个线程调用,请不要使用`synchronized`关键字。
`synchronized`关键字会影响应用的性能,因此您只能在并发环境中修改共享数据的方法上使用它。如果有多个线程调用`synchronized`方法,则一次只有一个线程执行它们,而其他线程将等待。如果操作不使用`synchronized`关键字,则所有线程可以同时执行该操作,从而减少总执行时间。如果您知道一个方法不会被多个线程调用,请不要使用`synchronized`关键字。
您可以将递归调用与`synchronized`方法一起使用。由于线程可以访问对象的`synchronized`方法,因此可以调用该对象的其他`synchronized`方法,包括正在执行的方法。它不必再次访问`synchronized`方法。
我们可以使用`synchronized`关键字来保护对代码块的访问,而不是对整个方法的访问。我们应该以这种方式使用`synchronized`关键字来保护对共享数据的访问,将其余操作排除在该块之外,从而获得更好的应用程序性能。目标是使关键部分(一次只能由一个线程访问的代码块)尽可能短。我们使用了`synchronized`关键字来保护对更新大楼中人数的指令的访问,省去了该块不使用共享数据的长时间操作。以这种方式使用`synchronized`关键字时,必须将对象引用作为参数传递。只有一个线程可以访问该对象的`synchronized`代码(块或方法)。通常,我们将使用`this`关键字引用正在执行该方法的对象。
我们可以使用`synchronized`关键字来保护对代码块的访问,而不是对整个方法的访问。我们应该以这种方式使用`synchronized`关键字来保护对共享数据的访问,将其余操作排除在该块之外,从而获得更好的应用性能。目标是使关键部分(一次只能由一个线程访问的代码块)尽可能短。我们使用了`synchronized`关键字来保护对更新大楼中人数的指令的访问,省去了该块不使用共享数据的长时间操作。以这种方式使用`synchronized`关键字时,必须将对象引用作为参数传递。只有一个线程可以访问该对象的`synchronized`代码(块或方法)。通常,我们将使用`this`关键字引用正在执行该方法的对象。
```java
synchronized (this) {
......@@ -459,7 +459,7 @@
在本例中,我们有一个对象控制对`vacanciesCinema1`属性的访问,因此每次只有一个线程可以修改该属性,另一个对象控制对`vacanciesCinema2`属性的访问,因此每次只有一个线程可以修改该属性。但是可能有两个线程同时运行,一个修改`vacancesCinema1`属性,另一个修改`vacanciesCinema2`属性。
运行此示例时,您可以看到最终结果始终是每个电影院的预期空缺数。在以下屏幕截图中,您可以看到应用程序执行的结果:
运行此示例时,您可以看到最终结果始终是每个电影院的预期空缺数。在以下屏幕截图中,您可以看到应用执行的结果:
![How it works...](img/7881_02_03.jpg)
......@@ -740,7 +740,7 @@ Java 为代码块的同步提供了另一种机制。这是一种比`synchronize
}
```
10. 通过实现名为`Main`的类来创建应用程序的主类,并向其添加`main()`方法。
10. 通过实现名为`Main`的类来创建应用的主类,并向其添加`main()`方法。
```java
public class Main {
......@@ -787,13 +787,13 @@ Java 为代码块的同步提供了另一种机制。这是一种比`synchronize
### 注
考虑到程序员有责任考虑此方法的结果并采取相应的行动。如果该方法返回`false`值,则您的程序将不会执行临界段。如果是这样,您的应用程序中可能会出现错误的结果。
考虑到程序员有责任考虑此方法的结果并采取相应的行动。如果该方法返回`false`值,则您的程序将不会执行临界段。如果是这样,您的应用中可能会出现错误的结果。
`ReentrantLock`类还允许使用递归调用。当线程控制锁并进行递归调用时,它将继续控制锁,因此对`lock()`方法的调用将立即返回,线程将继续执行递归调用。此外,我们还可以调用其他方法。
### 更多信息
您必须非常小心使用`Locks`以避免**死锁**。当两个或多个线程被阻塞,等待永远不会解锁的锁时,就会发生这种情况。例如,线程(a)锁定锁(X),线程(B)锁定锁(Y)。如果现在线程(A)尝试锁定锁(Y),线程(B)同时尝试锁定锁(X),这两个线程将被无限期阻塞,因为它们正在等待永远不会释放的锁。请注意,出现这个问题是因为两个线程都试图以相反的顺序获得锁。附录*并发编程设计*解释了充分设计并发应用程序和避免这些死锁问题的一些好技巧。
您必须非常小心使用`Locks`以避免**死锁**。当两个或多个线程被阻塞,等待永远不会解锁的锁时,就会发生这种情况。例如,线程(a)锁定锁(X),线程(B)锁定锁(Y)。如果现在线程(A)尝试锁定锁(Y),线程(B)同时尝试锁定锁(X),这两个线程将被无限期阻塞,因为它们正在等待永远不会释放的锁。请注意,出现这个问题是因为两个线程都试图以相反的顺序获得锁。附录*并发编程设计*解释了充分设计并发应用和避免这些死锁问题的一些好技巧。
## 另见
......
# 第三章线程同步实用程序
# 三、线程同步工具
在本章中,我们将介绍:
......@@ -29,7 +29,7 @@
* **Phaser**`Phaser`类是 Java 语言提供的另一种机制,用于控制分阶段执行的并发任务。所有线程必须先完成一个阶段,然后才能继续下一个阶段。这是 Java7API 的一个新特性。
* **交换机**`Exchanger`类是 Java 语言提供的另一种机制,提供两个线程之间的数据交换点。
信号量是一种通用的同步机制,您可以使用它来保护任何问题中的任何关键部分。其他机制被认为用于具有特定功能的应用程序中,如前所述。请确保根据应用程序的特点选择适当的机制。
信号量是一种通用的同步机制,您可以使用它来保护任何问题中的任何关键部分。其他机制被认为用于具有特定功能的应用中,如前所述。请确保根据应用的特点选择适当的机制。
本章介绍七种配方,向您展示如何使用所描述的机制。
......@@ -358,7 +358,7 @@
## 另见
* [第 3 章](3.html "Chapter 3. Thread Synchronization Utilities")*线程同步实用程序*中的*控制对资源*配方的并发访问
* [第 3 章](3.html "Chapter 3. Thread Synchronization Utilities")*线程同步工具*中的*控制对资源*配方的并发访问
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用*中的*监控锁接口*配方
* [第 2 章](2.html "Chapter 2. Basic Thread Synchronization")*基本线程同步*中的*修改锁公平性*配方
......@@ -563,7 +563,7 @@ Java 并发 API 提供了一个类,允许一个或多个线程等待一组操
# 在公共点同步任务
Java 并发 API 提供了一个同步实用程序,允许在一个确定的点上同步两个或多个线程。是的`CyclicBarrier`班。这个类类似于本章*等待多个并发事件*配方中解释的`CountDownLatch`类,但存在一些差异,使它们成为一个更强大的类。
Java 并发 API 提供了一个同步工具,允许在一个确定的点上同步两个或多个线程。是的`CyclicBarrier`班。这个类类似于本章*等待多个并发事件*配方中解释的`CountDownLatch`类,但存在一些差异,使它们成为一个更强大的类。
`CyclicBarrier`类用一个整数初始化,整数是将在一个确定点上同步的线程数。当其中一个线程到达确定的点时,它调用`await()`方法等待其他线程。当线程调用该方法时,`CyclicBarrier`类将阻塞正在休眠的线程,直到其他线程到达。当最后一个线程调用`CyclicBarrier`类的`await()`方法时,它会唤醒所有正在等待的线程并继续其作业。
......@@ -826,7 +826,7 @@ Java 并发 API 提供了一个同步实用程序,允许在一个确定的点
public static void main(String[] args) {
```
33. 声明并初始化五个常量来存储应用程序的参数。
33. 声明并初始化五个常量来存储应用的参数。
```java
final int ROWS=10000;
......@@ -898,7 +898,7 @@ Java 并发 API 提供了一个同步实用程序,允许在一个确定的点
`CyclicBarrier`类与`CountDownLatch`类有一些共同点,但也有一些差异。最重要的区别之一是,`CyclicBarrier`对象可以重置为其初始状态,并将初始化时使用的值分配给其内部计数器。
此重置操作可使用`CyclicBarrier`类的`reset()`方法完成。发生这种情况时,`await()`方法中等待的所有线程都会收到一个`BrokenBarrierException`异常。在本配方中的示例中,通过打印堆栈跟踪来处理此异常,但在更复杂的应用程序中,它可以执行一些其他操作,例如重新启动执行或在中断点恢复操作。
此重置操作可使用`CyclicBarrier`类的`reset()`方法完成。发生这种情况时,`await()`方法中等待的所有线程都会收到一个`BrokenBarrierException`异常。在本配方中的示例中,通过打印堆栈跟踪来处理此异常,但在更复杂的应用中,它可以执行一些其他操作,例如重新启动执行或在中断点恢复操作。
### 自行车运载器物体破损
......@@ -908,13 +908,13 @@ Java 并发 API 提供了一个同步实用程序,允许在一个确定的点
## 另见
* [第三章](3.html "Chapter 3. Thread Synchronization Utilities")*线程同步实用程序*中的*等待多个并发事件*配方
* [第三章](3.html "Chapter 3. Thread Synchronization Utilities")*线程同步工具*中的*等待多个并发事件*配方
# 运行并发阶段性任务
Java 并发 API 提供的最复杂、最强大的功能之一是使用`Phaser`类执行并发阶段性任务的能力。当我们将一些并发任务划分为多个步骤时,此机制非常有用。`Phaser`类为我们提供了在每个步骤结束时同步线程的机制,因此在所有线程完成第一步之前,没有线程开始第二步。
与其他同步实用程序一样,我们必须使用参与同步操作的任务数初始化`Phaser`类,但我们可以通过增加或减少该数目来动态修改该数目。
与其他同步工具一样,我们必须使用参与同步操作的任务数初始化`Phaser`类,但我们可以通过增加或减少该数目来动态修改该数目。
在本食谱中,您将学习如何使用`Phaser`类同步三个并发任务。这三个任务在三个不同的文件夹及其子文件夹中查找在过去 24 小时内修改了扩展名为`.log`的文件。此任务分为三个步骤:
......@@ -1213,13 +1213,13 @@ Java 并发 API 提供的最复杂、最强大的功能之一是使用`Phaser`
* **活动**`Phaser`在每个阶段结束时接受新参与者的注册和同步时进入此状态。在这种状态下,`Phaser`的工作原理如本配方所述。Java 并发 API 中没有提到这种状态。
* **终止**:默认情况下,`Phaser``Phaser`中的所有参与者都已注销时进入此状态,因此`Phaser`中没有参与者。更详细地说,`onAdvance()`方法返回`true`值时`Phaser`处于终止状态。如果重写该方法,则可以更改默认行为。当`Phaser`处于该状态时,同步方法`arriveAndAwaitAdvance()`立即返回,不进行任何同步操作。
`Phaser`类的一个显著特征是,您不必控制与 phaser 相关的方法的任何异常。与其他同步实用程序不同,在移相器中休眠的线程不会响应中断事件,也不会引发`InterruptedException`异常。只有一个例外在下面的*部分有更多的*部分解释。
`Phaser`类的一个显著特征是,您不必控制与 phaser 相关的方法的任何异常。与其他同步工具不同,在移相器中休眠的线程不会响应中断事件,也不会引发`InterruptedException`异常。只有一个例外在下面的*部分有更多的*部分解释。
以下屏幕截图显示了一次执行示例的结果:
![How it works...](img/7881_03_04.jpg)
它显示了执行的前两个阶段。您可以看到**应用程序**线程如何在第二阶段完成其执行,因为其结果列表为空。当您执行该示例时,您将看到一些线程如何在剩余阶段之前完成一个阶段,但它们会等到所有线程都完成一个阶段后再继续执行剩余阶段。
它显示了执行的前两个阶段。您可以看到**应用**线程如何在第二阶段完成其执行,因为其结果列表为空。当您执行该示例时,您将看到一些线程如何在剩余阶段之前完成一个阶段,但它们会等到所有线程都完成一个阶段后再继续执行剩余阶段。
## 还有更多。。。
......@@ -1246,7 +1246,7 @@ Java 并发 API 提供的最复杂、最强大的功能之一是使用`Phaser`
## 另见
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用程序*中的*监控移相器*配方
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用*中的*监控移相器*配方
# 控制并发阶段性任务中的阶段变化
......@@ -1495,12 +1495,12 @@ Java 并发 API 提供的最复杂、最强大的功能之一是使用`Phaser`
## 另见
* [第 3 章](3.html "Chapter 3. Thread Synchronization Utilities")*线程同步实用程序*中的*运行并发阶段任务*配方
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用程序*中的*监控移相器*配方
* [第 3 章](3.html "Chapter 3. Thread Synchronization Utilities")*线程同步工具*中的*运行并发阶段任务*配方
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用*中的*监控移相器*配方
# 在并发任务之间更改数据
Java 并发 API 提供了一个同步实用程序,允许在两个并发任务之间交换数据。更详细地说,`Exchanger`类允许定义两个线程之间的同步点。当两个线程到达这一点时,它们交换一个数据结构,因此第一个线程的数据结构转到第二个线程,第二个线程的数据结构转到第一个线程。
Java 并发 API 提供了一个同步工具,允许在两个并发任务之间交换数据。更详细地说,`Exchanger`类允许定义两个线程之间的同步点。当两个线程到达这一点时,它们交换一个数据结构,因此第一个线程的数据结构转到第二个线程,第二个线程的数据结构转到第一个线程。
在类似于生产者-消费者问题的情况下,此类可能非常有用。这是一个典型的并发问题,其中有一个公共数据缓冲区、一个或多个数据生产者和一个或多个数据消费者。由于`Exchanger`类只同步两个线程,如果一个生产者和一个消费者出现生产者-消费者问题,您可以使用它。
......@@ -1682,7 +1682,7 @@ Java 并发 API 提供了一个同步实用程序,允许在两个并发任务
此时,两个线程(生产者和消费者)都在`Exchanger`中,并且它改变了数据结构,因此当消费者从`exchange()`方法返回时,它将有一个包含 10 个字符串的缓冲区。当生产者从`exchange()`方法返回时,它将有一个空缓冲区再次填充。此操作将重复 10 次。
如果执行该示例,您将看到生产者和消费者是如何并发地执行其工作的,以及这两个对象在每个步骤中是如何交换缓冲区的。与其他同步实用程序一样,调用`exchange()`方法的第一个线程被置于睡眠状态,直到其他线程到达。
如果执行该示例,您将看到生产者和消费者是如何并发地执行其工作的,以及这两个对象在每个步骤中是如何交换缓冲区的。与其他同步工具一样,调用`exchange()`方法的第一个线程被置于睡眠状态,直到其他线程到达。
## 还有更多。。。
......
# 第 4 章线程执行器
# 四、线程执行器
在本章中,我们将介绍:
......@@ -16,10 +16,10 @@
# 导言
通常,当您用 Java 开发一个简单的并发编程应用程序时,您会创建一些`Runnable`对象,然后创建相应的`Thread`对象来执行它们。如果您必须开发一个运行大量并发任务的程序,这种方法有以下缺点:
通常,当您用 Java 开发一个简单的并发编程应用时,您会创建一些`Runnable`对象,然后创建相应的`Thread`对象来执行它们。如果您必须开发一个运行大量并发任务的程序,这种方法有以下缺点:
* 您必须实现所有与代码相关的信息来管理`Thread`对象(创建、结束、获取结果)。
* 每个任务创建一个`Thread`对象。如果必须执行大量任务,这可能会影响应用程序的吞吐量。
* 每个任务创建一个`Thread`对象。如果必须执行大量任务,这可能会影响应用的吞吐量。
* 您必须有效地控制和管理计算机资源。如果创建的线程太多,可能会使系统饱和。
自 Java5 以来,Java 并发 API 提供了一种旨在解决问题的机制。该机制称为**执行器框架**,围绕`Executor`接口、其子接口`ExecutorService`和实现这两个接口的`ThreadPoolExecutor`类展开。
......@@ -188,7 +188,7 @@ Executor 框架的另一个重要优点是`Callable`接口。与`Runnable`接口
* `getActiveCount()`:此方法返回执行器中正在执行任务的线程数
* `getCompletedTaskCount()`:此方法返回执行者完成的任务数
`ThreadPoolExecutor`类和一般执行器的一个关键方面是,必须显式结束它。如果不这样做,执行器将继续执行,程序将不会结束。如果执行器没有要执行的任务,它将继续等待新任务,并且不会结束执行。Java 应用程序在其所有非守护进程线程完成执行之前不会结束,因此,如果不终止执行器,应用程序将永远不会结束。
`ThreadPoolExecutor`类和一般执行器的一个关键方面是,必须显式结束它。如果不这样做,执行器将继续执行,程序将不会结束。如果执行器没有要执行的任务,它将继续等待新任务,并且不会结束执行。Java 应用在其所有非守护进程线程完成执行之前不会结束,因此,如果不终止执行器,应用将永远不会结束。
要向执行者表明您想要完成它,您可以使用`ThreadPoolExecutor`类的`shutdown()`方法。当执行器完成所有挂起任务的执行时,它将完成其执行。调用`shutdown()`方法后,如果您尝试向执行者发送另一个任务,该任务将被拒绝,执行者将抛出`RejectedExecutionException`异常。
......@@ -216,13 +216,13 @@ Executor 框架的另一个重要优点是`Callable`接口。与`Runnable`接口
## 另见
* [第 4 章](4.html "Chapter 4. Thread Executors")*线程执行器*中配方的*控制被拒绝的任务*
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用程序*中的*监控执行器框架*配方
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用*中的*监控执行器框架*配方
# 创建固定大小的线程执行器
使用`Executors`类的`newCachedThreadPool()`方法创建的 basic`ThreadPoolExecutor`时,执行器一次运行的线程数可能会有问题。执行器会为接收到的每个任务创建一个新线程(如果没有池线程空闲),因此,如果发送大量任务且任务持续时间较长,则可能会导致系统过载,并导致应用程序性能低下。
使用`Executors`类的`newCachedThreadPool()`方法创建的 basic`ThreadPoolExecutor`时,执行器一次运行的线程数可能会有问题。执行器会为接收到的每个任务创建一个新线程(如果没有池线程空闲),因此,如果发送大量任务且任务持续时间较长,则可能会导致系统过载,并导致应用性能低下。
如果您想避免这个问题,`Executors`类提供了一个创建固定大小线程执行器的方法。此执行器具有最大线程数。如果发送的任务数超过线程数,则执行器将不会创建额外的线程,剩余的任务将被阻止,直到执行器有空闲线程为止。通过这种行为,您可以保证执行者不会导致应用程序性能低下。
如果您想避免这个问题,`Executors`类提供了一个创建固定大小线程执行器的方法。此执行器具有最大线程数。如果发送的任务数超过线程数,则执行器将不会创建额外的线程,剩余的任务将被阻止,直到执行器有空闲线程为止。通过这种行为,您可以保证执行者不会导致应用性能低下。
在这个配方中,您将学习如何创建一个固定大小的线程执行器,修改本章第一个配方中实现的示例。
......@@ -274,7 +274,7 @@ Executor 框架的另一个重要优点是`Callable`接口。与`Runnable`接口
## 另见
* [第 4 章](4.html "Chapter 4. Thread Executors")*线程执行器*中的*创建线程执行器*配方
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用程序*中的*监控执行器框架*配方
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用*中的*监控执行器框架*配方
# 在返回结果的执行器中执行任务
......@@ -696,7 +696,7 @@ Executor 框架的优点之一是可以运行返回结果的并发任务。Java
如果您多次运行示例,您将得到四种可能的解决方案。
以下屏幕截图显示了当两个任务都引发异常时应用程序的输出:
以下屏幕截图显示了当两个任务都引发异常时应用的输出:
![How it works...](img/7881_04_04.jpg)
......
# 第 5 章分叉/连接框架
# 五、Fork/Join框架
在本章中,我们将介绍:
......@@ -10,7 +10,7 @@
# 导言
通常,当您实现一个简单的并发 Java 应用程序时,您会实现一些`Runnable`对象,然后是相应的`Thread`对象。您可以控制程序中这些线程的创建、执行和状态。Java5 对`Executor``ExecutorService`接口以及实现它们的类(例如`ThreadPoolExecutor`类)进行了改进。
通常,当您实现一个简单的并发 Java 应用时,您会实现一些`Runnable`对象,然后是相应的`Thread`对象。您可以控制程序中这些线程的创建、执行和状态。Java5 对`Executor``ExecutorService`接口以及实现它们的类(例如`ThreadPoolExecutor`类)进行了改进。
Executor 框架将任务创建和执行分开。使用它,您只需实现`Runnable`对象并使用`Executor`对象。您将`Runnable`任务发送给执行器,执行器将创建、管理和完成执行这些任务所需的线程。
......@@ -27,7 +27,7 @@ Java7 更进一步,包括面向特定问题的`ExecutorService`接口的附加
* **fork**操作:将任务划分为更小的任务,并使用框架执行它们
* **加入**操作:当任务等待其创建的任务完成时
Fork/Join 与 Executor 框架的主要区别在于**工作窃取**算法。与 Executor 框架不同,当任务等待使用联接操作创建的子任务完成时,执行该任务的线程(称为**工作线程**查找尚未执行的其他任务并开始执行。通过这种方式,线程可以充分利用其运行时间,从而提高应用程序的性能。
Fork/Join 与 Executor 框架的主要区别在于**工作窃取**算法。与 Executor 框架不同,当任务等待使用联接操作创建的子任务完成时,执行该任务的线程(称为**工作线程**查找尚未执行的其他任务并开始执行。通过这种方式,线程可以充分利用其运行时间,从而提高应用的性能。
为了实现这一目标,Fork/Join 框架执行的任务有以下限制:
......@@ -336,7 +336,7 @@ Fork/Join 框架的核心由以下两个类组成:
## 另见
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用程序*中的*监控 Fork/Join 池*配方
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用*中的*监控 Fork/Join 池*配方
# 加入任务结果
......@@ -358,7 +358,7 @@ If (problem size > size){
如果任务必须解决大于预定义大小的问题,则可以将问题划分为更多子任务,并使用 Fork/Join 框架执行这些子任务。当它们完成执行时,发起任务获得所有子任务生成的结果,对它们进行分组,并返回最终结果。最终,当池中执行的初始任务完成其执行时,您将获得其结果,这实际上是整个问题的最终结果。
在本教程中,您将学习如何使用 Fork/Join 框架开发一个在文档中查找单词的应用程序来解决此类问题。您将执行以下两种任务:
在本教程中,您将学习如何使用 Fork/Join 框架开发一个在文档中查找单词的应用来解决此类问题。您将执行以下两种任务:
* 文档任务,它将在文档的一组行中搜索一个单词
* 一个行任务,它将在文档的一部分中搜索一个单词
......@@ -717,13 +717,13 @@ If (problem size > size){
## 另见
* [第 5 章](5.html "Chapter 5. Fork/Join Framework")*Fork/Join 框架*中的*创建 Fork/Join 池*配方
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用程序*中的*监控 Fork/Join 池*配方
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用*中的*监控 Fork/Join 池*配方
# 异步运行任务
当在`ForkJoinPool`中执行`ForkJoinTask`时,可以同步或异步执行。当您以同步方式执行时,将任务发送到池的方法不会返回,直到发送的任务完成其执行。以异步方式执行时,将任务发送给执行器的方法会立即返回,因此任务可以继续执行。
您应该意识到这两种方法之间的巨大差异。当您使用同步方法时,调用其中一个方法(例如,`invokeAll()`方法)的任务将被挂起,直到它发送到池中的任务完成执行为止。这允许`ForkJoinPool`类使用工作窃取算法将新任务分配给执行休眠任务的工作线程。相反,当您使用异步方法(例如,`fork()`方法)时,任务将继续执行,因此`ForkJoinPool`类不能使用工作窃取算法来提高应用程序的性能。在这种情况下,只有在调用`join()``get()`方法等待任务完成时,`ForkJoinPool`类才能使用该算法。
您应该意识到这两种方法之间的巨大差异。当您使用同步方法时,调用其中一个方法(例如,`invokeAll()`方法)的任务将被挂起,直到它发送到池中的任务完成执行为止。这允许`ForkJoinPool`类使用工作窃取算法将新任务分配给执行休眠任务的工作线程。相反,当您使用异步方法(例如,`fork()`方法)时,任务将继续执行,因此`ForkJoinPool`类不能使用工作窃取算法来提高应用的性能。在这种情况下,只有在调用`join()``get()`方法等待任务完成时,`ForkJoinPool`类才能使用该算法。
在这个配方中,您将学习如何使用`ForkJoinPool``ForkJoinTask`类提供的异步方法来管理任务。您将要实现一个程序,该程序将在文件夹及其子文件夹中搜索具有确定扩展名的文件。您将要实现的`ForkJoinTask`类将处理文件夹的内容。对于该文件夹中的每个子文件夹,它将以异步方式向`ForkJoinPool`类发送一个新任务。对于该文件夹中的每个文件,任务将检查文件的扩展名,并在继续时将其添加到结果列表中。
......@@ -953,7 +953,7 @@ If (problem size > size){
## 另见
* [第 5 章](5.html "Chapter 5. Fork/Join Framework")*Fork/Join 框架*中的*创建 Fork/Join 池*配方
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用程序*中的*监控 Fork/Join 池*配方
* [第 8 章](8.html "Chapter 8. Testing Concurrent Applications")*测试并发应用*中的*监控 Fork/Join 池*配方
# 在任务中抛出异常
......
# 第六章同时收款
# 六、并发
在本章中,我们将介绍:
......@@ -15,14 +15,14 @@
**数据结构**是编程的基本元素。几乎每个程序都使用一种或多种类型的数据结构来存储和管理数据。Java API 提供了包含接口、类和算法的**Java 集合框架**,这些框架实现了许多不同的数据结构,可以在程序中使用。
当您需要在并发程序中处理数据收集时,必须非常小心地选择实现。大多数集合类不准备与并发应用程序一起工作,因为它们不控制对其数据的并发访问。如果某些并发任务共享的数据结构不适合处理并发任务,则可能会出现数据不一致错误,从而影响程序的正确操作。这种数据结构的一个例子是`ArrayList`类。
当您需要在并发程序中处理数据收集时,必须非常小心地选择实现。大多数集合类不准备与并发应用一起工作,因为它们不控制对其数据的并发访问。如果某些并发任务共享的数据结构不适合处理并发任务,则可能会出现数据不一致错误,从而影响程序的正确操作。这种数据结构的一个例子是`ArrayList`类。
Java 提供了可以在并发程序中使用的数据集合,而不会出现任何问题或不一致。Java 基本上提供了两种在并发应用程序中使用的集合:
Java 提供了可以在并发程序中使用的数据集合,而不会出现任何问题或不一致。Java 基本上提供了两种在并发应用中使用的集合:
* **阻塞收集**:这种类型的收集包括添加和删除数据的操作。如果由于集合已满或为空而无法立即执行该操作,则发出调用的线程将被阻止,直到可以执行该操作为止。
* **非阻塞收集**:这种收集还包括添加和删除数据的操作。如果不能立即执行该操作,则该操作返回一个`null`值或抛出一个异常,但发出调用的线程不会被阻塞。
通过本章的介绍,您将学习如何在并发应用程序中使用一些 Java 集合。这包括:
通过本章的介绍,您将学习如何在并发应用中使用一些 Java 集合。这包括:
* 非阻塞列表,使用`ConcurrentLinkedDeque`
* 阻止列表,使用`LinkedBlockingDeque`
......@@ -558,7 +558,7 @@ Java 提供了可以在并发程序中使用的数据集合,而不会出现任
# 使用具有延迟元素的线程安全列表
`DelayedQueue`类中实现了 Java API 提供的一个有趣的数据结构,您可以在并发应用程序中使用它。在这个类中,您可以存储带有激活日期的元素。返回或提取队列元素的方法将忽略其数据在将来的元素。这些方法看不见它们。
`DelayedQueue`类中实现了 Java API 提供的一个有趣的数据结构,您可以在并发应用中使用它。在这个类中,您可以存储带有激活日期的元素。返回或提取队列元素的方法将忽略其数据在将来的元素。这些方法看不见它们。
要获得此行为,您要存储在`DelayedQueue`类中的元素必须实现`Delayed`接口。此接口允许您处理延迟对象,因此您将实现`DelayedQueue`类中存储的对象的激活日期,作为激活日期之前的剩余时间。此接口强制实现以下两种方法:
......@@ -978,9 +978,9 @@ Java API 还提供了一个实现该接口的类,即实现具有`ConcurrentNav
# 生成并发随机数
Java 并发 API 提供了在并发应用程序中生成伪随机数的特定类。它是`ThreadLocalRandom`类,在 Java7 版本中是新的。它作为线程局部变量工作。每个想要生成随机数的线程都有一个不同的生成器,但是它们都是从同一个类中管理的,对程序员来说是透明的。使用此机制,您将获得比使用共享`Random`对象生成所有线程的随机数更好的性能。
Java 并发 API 提供了在并发应用中生成伪随机数的特定类。它是`ThreadLocalRandom`类,在 Java7 版本中是新的。它作为线程局部变量工作。每个想要生成随机数的线程都有一个不同的生成器,但是它们都是从同一个类中管理的,对程序员来说是透明的。使用此机制,您将获得比使用共享`Random`对象生成所有线程的随机数更好的性能。
在本教程中,您将学习如何使用`ThreadLocalRandom`类在并发应用程序中生成随机数。
在本教程中,您将学习如何使用`ThreadLocalRandom`类在并发应用中生成随机数。
## 准备好了吗
......@@ -1260,7 +1260,7 @@ Java 并发 API 提供了在并发应用程序中生成伪随机数的特定类
# 使用原子阵列
当您实现一个并发应用程序,其中一个或多个对象由多个线程共享时,您必须使用同步机制作为锁或`synchronized`关键字来保护对其属性的访问,以避免数据不一致错误。
当您实现一个并发应用,其中一个或多个对象由多个线程共享时,您必须使用同步机制作为锁或`synchronized`关键字来保护对其属性的访问,以避免数据不一致错误。
这些机制存在以下问题:
......
# 第 7 章定制并发类
# 七、自定义并发类
在本章中,我们将介绍:
......@@ -15,9 +15,9 @@
# 导言
Java 并发 API 提供了许多接口和类来实现并发应用程序。它们提供低级机制,如`Thread`类、`Runnable``Callable`接口或`synchronized`关键字,以及高级机制,如 Java 7 版本中添加的 Executor 框架和 Fork/Join 框架。尽管如此,您可能会发现自己开发的程序中没有一个 java 类满足您的需求。
Java 并发 API 提供了许多接口和类来实现并发应用。它们提供低级机制,如`Thread`类、`Runnable``Callable`接口或`synchronized`关键字,以及高级机制,如 Java 7 版本中添加的 Executor 框架和 Fork/Join 框架。尽管如此,您可能会发现自己开发的程序中没有一个 java 类满足您的需求。
在这种情况下,您可能需要基于 Java 提供的工具实现自己的定制并发实用程序。基本上,你可以:
在这种情况下,您可能需要基于 Java 提供的工具实现自己的定制并发工具。基本上,你可以:
* 实现一个接口以提供该接口定义的功能。例如,`ThreadFactory`接口。
* 重写类的某些方法以使其行为适应您的需要。例如,重写`Thread`类的`run()`方法,默认情况下,该方法没有任何用处,应该被重写以提供一些功能。
......@@ -231,7 +231,7 @@ Executor 框架是一种机制,允许您将线程创建与其执行分离。
# 实现基于优先级的执行器类
在 Java 并发 API 的第一个版本中,您必须创建并运行应用程序的所有线程。在 Java 版本 5 中,随着 Executor 框架的出现,引入了一种新的并发任务执行机制。
在 Java 并发 API 的第一个版本中,您必须创建并运行应用的所有线程。在 Java 版本 5 中,随着 Executor 框架的出现,引入了一种新的并发任务执行机制。
使用 Executor 框架,您只需实现任务并将其发送给 Executor 即可。执行器负责创建和执行执行任务的线程。
......@@ -400,7 +400,7 @@ Executor 框架是一种机制,允许您将线程创建与其执行分离。
* 使用此工厂,我们可以集中创建对象,从而可以轻松更改创建的对象的类别或创建这些对象的方式,从而轻松地限制为有限资源创建对象。例如,我们只能有一种类型的*N*对象,这种类型的对象很容易生成有关对象创建的统计数据。
Java 提供了`ThreadFactory`接口来实现`Thread`对象工厂。Java 并发 API 的一些高级实用程序,如 Executor 框架或 Fork/Join 框架,使用线程工厂创建线程。
Java 提供了`ThreadFactory`接口来实现`Thread`对象工厂。Java 并发 API 的一些高级工具,如 Executor 框架或 Fork/Join 框架,使用线程工厂创建线程。
Java 并发 API 中工厂模式的另一个例子是`Executors`类。它提供了许多方法来创建不同类型的`Executor`对象。
......@@ -1701,9 +1701,9 @@ Java 并发 API 提供了另一个类来实现同步机制。它是`AbstractQueu
# 实现基于优先级的传输队列
Java7API 提供了几种数据结构来处理并发应用程序。在此基础上,我们希望强调以下两种数据结构:
Java7API 提供了几种数据结构来处理并发应用。在此基础上,我们希望强调以下两种数据结构:
* `LinkedTransferQueue`:这个数据结构应该用于那些具有生产者/消费者结构的程序中。在这些应用程序中,您有一个或多个数据生产者和一个或多个数据消费者,并且所有人共享一个数据结构。生产者将数据放入数据结构中,消费者从数据结构中获取数据。如果数据结构为空,则会阻止使用者,直到他们有数据可使用为止。如果数据结构已满,生产者将被阻止,直到他们有空间放置数据。
* `LinkedTransferQueue`:这个数据结构应该用于那些具有生产者/消费者结构的程序中。在这些应用中,您有一个或多个数据生产者和一个或多个数据消费者,并且所有人共享一个数据结构。生产者将数据放入数据结构中,消费者从数据结构中获取数据。如果数据结构为空,则会阻止使用者,直到他们有数据可使用为止。如果数据结构已满,生产者将被阻止,直到他们有空间放置数据。
* `PriorityBlockingQueue`:在这个数据结构中,元素是按顺序存储的。元素必须使用`compareTo()`方法实现`Comparable`接口。在结构中插入图元时,会将其与结构的图元进行比较,直到找到其位置。
`LinkedTransferQueue`元素的存储顺序与它们到达时的顺序相同,因此先消耗较早到达的元素。当您想要开发生产者/消费者程序时,可能会出现这种情况,其中数据是根据某种优先级而不是到达时间消耗的。在此配方中,您将学习如何实现生产者/消费者问题中使用的数据结构,其元素将按优先级排序。优先级较高的元素将首先使用。
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册