10.1 模式简介
- 小孩子在玩玩具时经常会将玩具弄得满房间都是。晚上到了睡觉时间,妈妈就会对小孩子说:“先收拾房间再睡觉哦。” 这时,小孩子会开始打扫房间。
- 该模式的名字直译为中文是 “分两阶段终止” 的意思。它是一种先执行完终止处理再终止线程的模式。

- 我们称线程在进行正常处理时的状态为 “操作中”。在要停止该线程时,我们会发出 “终止请求”。这样,线程就不会突然终止,而是会先开始进行 “打扫工作”。我们称这种状态为 “终止处理中”。从 “操作中” 变为 “终止处理中” 是线程终止的第一阶段。
- 在 “终止处理中” 状态下,线程不会再进行正常操作了。它虽然仍然在运行,但是只会进行终止处理。终止处理完成后,就会真正地终止线程。“终止处理中” 状态结束是线程终止的第二阶段。
- 先从 “操作中” 状态变为 “终止处理中” 状态,然后再真正地终止线程。这就是 Two-Phase Termination模式。
- 该模式的要点如下:
- 安全地终止线程(安全性)
- 必定会进行终止处理(生存性)
- 发出终止请求后尽快进行终止处理(响应性)
10.3 Two-Phase Termination模式中的角色
10.3.1 TerminationRequester(终止请求发出者)
- TerminationRequester 角色负责向 Terminator 角色发出终止请求。
10.3.2 Terminator(终止者)
- Terminator 角色负责接收终止请求,并实际执行终止处理。它提供了表示终止请求的shutdownRequest方法。shutdownRequest方法不需要使用Single Threaded Execution模式。
- 当shutdownRequest方法被调用后,Terminator角色会在考虑了安全性的基础上,自己进入 “终止处理中” 状态。接着,当终止处理结束后,Terminator角色就会终止自己。
- Terminator角色带有一个表示自己是否已经接收到终止请求的标志(门),在需要安全地开始终止处理时,会检查这个标志。如果能够频繁地检查该标志,就可以缩短接收到终止请求后变为“终止处理中 ”状态所需的时间。
10.4 拓展思路的要点
10.4.1 不能使用Thread类的stop方法
- java.lang.Thread类提供了用于强制终止线程的stop方法。但是stop是 “不推荐使用的方法”(deprecated方法),我们不应当使用它。
- 因为如果使用stop方法,实例的安全性就无法确保。使用stop方法后,线程会在抛出 Java.lang.ThreadDeath异常后终止。即使该线程正处于访问临界区的过程中(例如正在执行synchronized方法的过程中)也会终止。请看以下示例:
class Position
{
private int x;
private int y;
public synchronized void setXY (int newx, int newY)
{
x = newx;
y = newY;
}
}
- 如果使用 stop 方法终止线程,那么实例就会失去安全性。这是因为,线程可能会在赋值语句
x = newx
执行之后、赋值语句 y=newY
执行之前这个时间点终止。
10.4.2 仅仅检查标志是不够的
- 让我们来思考一下为何需要在 shutdownRequest 方法中调用 interrupt 方法。换言之,就是思考一下为什么只将 shutdownRequested 标志设为true是不行的。
- 原因很简单。因为当想要终止线程时,该线程可能正在sleep。而当线程正在sleep时,即使将shutdownRequested 标志设置为true,线程也不会开始终止处理。等到sleep时间过后,线程可能会在某个时间点开始终止处理,但是这样程序的响应性就下降了。如果使用interrupt方法的话,就可以中断sleep。
- 另外,线程当时也可能正在wait。而当线程正在wait时,即使将 shutdownRequested标志设为true,线程也不会从等待队列中出来,所以我们必须使用interrupt方法对线程下达 “中断wait” 的指示。
10.4.3 仅仅检查中断状态是不够的
- 调用interrupt方法后,如果线程正在sleep或是wait,那么会抛出 InterruptedException 异常,而如果不抛出异常,线程就会变为中断状态。那么能否不准备一个新的shutdownRequested 标志,而是通过捕获 InterruptedException,使用 isInterrupted 方法来检查线程是否处于中断状态?
- 如果开发人员可以看到线程的所有相关程序,那么就无需使用shutdownRequested标志。只要捕获InterruptedException,并使用 isInterrupted 方法就能够正确地开始终止处理。
- 但是,只要线程正在执行的方法中有一处忽略InterruptedException,上面的方法就可能行不通。“忽略InterruptedException” 是指像下面这样的代码片段:
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
}
- 这样,即使在wait、sleep、join状态时抛出了InterruptedException,线程也不会变为中断状态。也就是说,如果程序中没有shutdownRequested标志,而且有上面这样的代码,那么即使使用shutdownRequest方法发出了终止请求,该请求也不会被处理。
- shutdownRequested是用于记录是否已经发出终止请求的标志。(shutdownRequested 的意义)
10.4.4 优雅地终止线程
- “线程优雅地执行终止处理,然后终止运行” 这种状态用英语单词来形容的话,就是Graceful
(优雅的、高贵的、得体的)。这种状态相当于工作的结束并不是慌慌张张地放下已经着手的工作不管,而是在进行必要的整理后才正式终止。Two-Phase Termination模式就是用来优雅地终止线程的模式。下面我们来看看Two-Phase Termination模式是如何体现 安全性、生存性、响应性三个要点的。
10.4.4.1 安全地终止(安全性)
- 即使接收到终止请求,线程也不会立即终止。首先表示是否已经接收到终止请求的shutdownRequested标志会被设置为true。然后,仅在线程运行至不会破坏对象安全性的位置时,程序才会开始终止处理。
- 这就像是即使妈妈说了 “要睡觉了”,也不能慌慌张张地打扫房间导致弄坏玩具一样。
10.4.4.2 必定会进行终止处理(生存性)
- 线程在接收到终止请求后,会中断可以中断的 wait,转入终止处理。为此,shutdownRequest方法会调用 interrup t方法。
- 另外,为了确保在抛出异常后程序也会执行终止处理,我们使用了 try…finally 语句块。
- 这就像是不能让玩具散落一地就去睡觉一样。
10.4.4.3 发出终止请求后尽快进入终止处理(响应性)
- 线程在接收到终止请求后,会中断可以中断的 sleep,尽快进入终止处理。为此,shutdownRequest 方法会调用interrupt方法。
- 另外,在执行长时间处理前需要检查 shutdownRequested 标志。
- 这就像是如果被妈妈说了 “快收拾房间”,就要尽快地收拾房间。
10.7 java.util.concurrent 包与线程同步
10.7.1 java.util.concurrent.CountDownLatch类
- 使用 Java.util.concurrent.CountDownLatch类可以实现 “等待指定次数的CountDown方法被调用” 这一功能。

10.7.2 java.util.concurrent.CyclicBarrier类
- CyclicBarrier可以周期性地(cyclic)创建出屏障(barrier)。在屏障解除之前,碰到屏障的线程是无法继续前进的。
- 屏障的解除条件是到达屏障处的线程个数达到了构造函数指定的个数。也就是说,当指定个数的线程到达屏障处后,屏障就会被解除,然后这些线程就会像听到了 “预备,走” 一样一起冲出去。
- 即可以用来实现 “除非 X 个线程都结束第 N 阶段的处理,否则哪个线程都不能进入第 N+1 阶段” 的功能。
