假设我们需要终⽌正在操作中的线程, ⽽且在终⽌前要执⾏特定的终⽌处理, 此外, 在进⼈终⽌处理前, 我们还不能破坏对象的安全性, 当出现这种需求时, 我们可以准备⼀个表示 "终⽌请求" 的⽅法, 并在该⽅法中设置⼀个表示是否已经接收到终⽌请求的标志, 线程会在开始终⽌处理前的⼀个安全点检查该标志, 如果已经接收到终⽌请求, 那么就开始终⽌处理, 是否进⼈终⽌处理最终由我们要终⽌的线程(Terminator⻆⾊)⾃身来判断
在Java 中, 我们可以使⽤ interrupt ⽅法去中断在 wait, sleep, join 各⽅法中等待着的线程, 由于调⽤ Thread.interrupted ⽅法后中断状态会被清除, 所以在检查线程是否接收到了终⽌请求时需要格外注意
该模式的名字直许为中⽂是 "分两份段终⽌" , 它是⼀种先执⾏完终⽌处理再後領的模式, 模式图如下
我们称线程在进⾏正常处理时的状态为 “操作中”, 在要停⽌该线程时, 我们会发出 "终⽌请求", 这样, 线程就不会突松终⽌, ⽽是会先开始进⾏ “打扫⼯作”, 我们称这种状态为 “终⽌处理中", 从 “操作中” 变为 “终⽌处理中” 是线程终⽌的第⼀阶段
在 "终止处理中" 状态下, 线程不再进行正常操作了, 他虽然仍在进行, 但是只会进行终止处理, 终止处理完成后, 就会真正的终止线程, "终止线程中" 状态结束是线程终止的第二阶段
先从 “操作中” 状态变为 “终⽌处理中” 状态, 然后再真正地终⽌线程, 这就是 Two-Phusc-Termination 模式
二. 示例程序| 类名 | 说明 |
| Main | 测试程序类 |
| CountupThread | 表示进行计数线程的类 |
Main 类会启动 CountupThread 的线程, 然后在⼤约10秒后终⽌该线程, 与往常⼀样, 我们使⽤ start ⽅法启动线程, 调⽤之前在 Countuprhread 类中声明的 shutdownRequest ⽅法可以发送线程终⽌请求, 然后, 该⽅法会⽴即返回, Thread 类的 join ⽅法是⽤于等待线程终⽌的⽅法, 在指定的线程终⽌前, join⽅法不会返回
public class Main {
public static void main(String[] args) {
System.out.println(" main begin");
try {
CountuptThread t = new CountupThread();
t.start();
Thread.sleep(10000);
System.out.println(" main shutdownRequest");
t.shutdownRequest();
System.out.println(" main join");
} catch (InterruptedException e) {
//
}
System.out.println(" main end");
}
}
2.2 CountupThread类
counter 字段表示当前的计数值
Sbutdown Reguested 字段是表示是否已经发出终⽌消求的标志, 该字段的值⽤于判新线程是否要进⼈“终⽌处理中”状态
BhutaowaReguest ⽅法是表示线程终⽌消求的⽅法, 当要终⽌ countupthread.的线程时, 程序会调⽤该⽅法
请注意 shutdlownReguest ⽅法不仅将 shutdownRequested标志设置为了为 true, 还调用了 interrupt ⽅法, 这是为了确保线程在, sleep 和 wait 时也会被终⽌
shutdownRequest ⽅法⽆需设置为 synchronized, 这是因为 shutdownReguested 标是⼀个⼀且被设置为为 true 后就不会再变为 false 的闭锁, 即使它被多个线程同时调⽤也不会有问题, 因此,shutdownRequest ⽅法带有 (coneuzzent) 约束
isShutdownRequested ⽅法会去检查 shutdownRequest ⽅法是否已经被调⽤ (即终⽌请求是否已经发出)
run ⽅法是实际的处理, 仅当 shutdownRequested 的值为 false 的期间内, 才会执⾏ while 循环, while 循环会调⽤执⾏实际操作的 dowork ⽅法, 接着, finally 语句块会调⽤执⾏终⽌处理的doshutdown ⽅法
doWork ⽅法是进⾏实际操作的⽅法, 这⾥会将 counter 的值加1 (即计数) 后显示出来
doShutdown ⽅法是执⾏终⽌处理的⽅法, 原本应该在这⾥编写终业线程时需要执⾏的处理, 但此处我们只是简单地显示 counter 的值
public class CountuptThread extends Thread {
// 发出终止请求后变为 false
private volatile boolean shutdownRequested = false;
// 计数器
private long counter = 0;
// 终止请求
public void shutdownRequest() {
shutdownRequested = true;
interrupt();
}
// 检查是否发出了终止请求
public boolean isShutdownRequested() {
return shutdownRequested;
}
// 线程体
@Override
public final void run() {
try {
while (!isShutdownRequested()) {
doWork();
}
} catch (InterruptedException e) {
//
} finally {
doShutdown();
}
}
private void doShutdown() {
System.out.println("doShutdown : counter =" + counter);
}
private void doWork() throws InterruptedException{
counter++;
System.out.println("doWork : counter =" + counter);
Thread.sleep(500);
}
}
2.3 运行示例
运行示例可以看出, 在终止请求发出后, doShutdown 方法就被调用了, shutdownRequested = true, 主程序也就终止了
main begin doWork : counter = 1 doWork : counter = 2 doWork : counter = 3 doWork : counter = 4 doWork : counter = 5 doWork : counter = 6 doWork : counter = 7 doWork : counter = 8 doWork : counter = 9 doWork : counter = 10 main shutdownRequest main join main end doShutdown : counter = 10三. Two-Phase Termination模式中的登场的角色 3.1 TerminationRequester(终⽌请求发出者)
TerminationRequester ⻆⾊负责向 Temninator ⻆⾊发出终⽌清求, 在示例程序中, 由 Main 类扮演此⻆⾊
3.2 Terminator (终⽌者)Terminator 角色负责接收终止请求, 并实际执行终止操作, 它提供了表示终止请求的 shutdownRequest 方法, 当该方法被调用后, Terminator 角色会考虑了安全性的基础上, 自己进入 "终止处理中" 状态, 接着, 当终止处理结束后, Terminator 角色就会终止自己, Terminator 角色带有一个表示自己是否已经接收到终止请求的标识, 在需要安全的开始终止处理时, 会检查这个标识, 如果能够频繁的检查该标识, 就可以缩短接收到终止请求后变为 "终止处理中" 状态所需要的时间
在示例程序中, 有 countupThread 类扮演此角色
3.3 模式类图 四. java.util.concurrent包与线程同步 4.1 通过java.util.concurrent.CountDonwLatch实现线程同步当我们想让某个线程等待指定的线程终⽌时, 可以使⽤ java.lang.Thread 类的 join ⽅法, 但由于join ⽅法可以等待的只是“线程终⽌”这个⼀次性的操作, 所以我们⽆法使⽤它实现 "等待指定次数的某种操作发⽣", 使⽤ java.util.concurrent.CountDownLatch 类可以实现 "等待指定次数的 CountDown⽅法被调⽤" 这⼀功能, 代码如下
public class Main2 {
private static final int TASKS = 10;
public static void main(String[] args) {
System.out.println(" begin");
ExecutorService executorService = Executors.newFixedThreadPool(5);
CountDownLatch countDownLatch = new CountDownLatch(TASKS);
try {
for (int i = 0; i < TASKS; i++) {
executorService.execute(new MyTask(countDownLatch, i));
}
System.out.println(" await ");
countDownLatch.await();
} catch (InterruptedException e) {
//
} finally {
executorService.shutdown();
System.out.println(" end ");
}
}
}
public class MyTask implements Runnable {
private final CountDownLatch countDownLatch;
private final int context;
private static final Random r = new Random(3111);
public MyTask(CountDownLatch countDownLatch, int context) {
this.countDownLatch = countDownLatch;
this.context = context;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " : MyTask : begin : context = " + context);
try {
Thread.sleep(r.nextInt(3000));
} catch (InterruptedException e) {
//
} finally {
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName() + " : MyTask : end : context = " + context);
}
}
}
4.2 通过java.util.concurrent.CyclicBarrier实现线程同步
前⾯的 CountDownLatch 类只能进⾏倒数计数, 也就是说, ⼀旦计数值变为0后, 即使调⽤ await ⽅法, 主线程也会⽴即返回
当想多次重复进⾏线程同步时, 使⽤ java.util.concurrent.cyclicBarrier 类会很⽅便, CyclicBarrier 可以周期性地(cyclic) 创建出屏障(barrier), 在屏障解除之前, 碰到屏障的线程是⽆法继续前进的, 屏障的解除条件是到达屏障处的线程个数达到了构造函数指定的个数, 也就是说, 当指定个数的线程到达屏障处后, 屏障就会被解除, 然后这些线程就会像听到了 “预备,走" ⼀样⼀起冲出去
代码展示的是使⽤了 CyclicBarrier 类的示例程序, 该程序实现了 "让三个线程处理⼀项分为0~4共五个阶段的⼯作" 的功能, 假设我们要实现 "除⾮三个线程都结束第N阶段的处理, 否则哪个线程都不能进⼈第 N+1 阶段" 的功能, 为了使线程能在这五个阶段中保持 "步调⼀致", 这⾥需要使⽤CyclicBarrier 类, 为了通知主线程 "所有⼯作的各个阶段都已结束", 这⾥还使⽤了前⾯的CountDownLatch 类, 在创建 cyclicBarrier 的实例时, 可以指定 Runnable 对象, 这个对象被称作 “屏障操作” (barier action), 每次屏障被解除后, 该屏障操作都会被执⾏
public class Main3 {
private static final int THREADS = 3;
public static void main(String[] args) {
System.out.println(" begin ");
ExecutorService executorService = Executors.newFixedThreadPool(THREADS);
Runnable barrierAction = () -> System.out.println(" Barrier Action");
// 让所有线程步调一致
CyclicBarrier cyclicBarrier = new CyclicBarrier(THREADS, barrierAction);
// 使用确认所有线程工作是否结束
CountDownLatch countDownLatch = new CountDownLatch(THREADS);
try {
for (int i = 0; i < THREADS; i++) {
executorService.execute(new MyTask2(cyclicBarrier, countDownLatch, i));
}
System.out.println(" await ");
countDownLatch.await();
} catch (InterruptedException e) {
//
} finally {
executorService.shutdown();
System.out.println(" end ");
}
}
}
public class MyTask2 implements Runnable {
private static final int PHASE = 5;
private final CyclicBarrier cyclicBarrier;
private final CountDownLatch countDownLatch;
private final int context;
private static final Random r = new Random(10000);
public MyTask2(CyclicBarrier cyclicBarrier, CountDownLatch countDownLatch, int context) {
this.cyclicBarrier = cyclicBarrier;
this.countDownLatch = countDownLatch;
this.context = context;
}
@Override
public void run() {
try {
for (int phase = 0; phase < PHASE; phase++) {
doPhase(phase);
cyclicBarrier.await();
}
} catch (InterruptedException | BrokenBarrierException e) {
//
}finally {
countDownLatch.countDown();
}
}
private void doPhase(int phase) {
System.out.println(Thread.currentThread().getName() + " : MyTask : begin : context = " + context + ", phase = " + phase);
try {
Thread.sleep(r.nextInt(3000));
} catch (InterruptedException e) {
//
} finally {
// 发出终止请求
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName() + " : MyTask : end : context = " + context + ", phase = " + phase);
}
}
}


