1、什么是 Two Phase Termination 模式
当一个线程正常结束,或者因被打断而结束,或者因出现异常而结束时,我们需要考虑 如何同时释放线程中资源,比如文件句柄、 Socket 套接字句柄、数据库连接等比较稀缺的 资源。 Two Phase Termination 设计模式可以帮助我们实现,如图所示
如图所示,我们使用“作业中”表示线程的执行状态,当希望结束这个线程时,发出线 程结束请求,接下来线程不会立即结束,而是会执行相应的资源释放动作直到真正的结束, 在终止处理状态时,线程虽然还在运行,但是进行的是终止处理工作,因此终止处理又称为 线程结束的第二个阶段,而受理终止要求则被称为线程结束的第一个阶段。
在进行线程两阶段终结的时候需要考虑如下几个问题
a 第二阶段的终止保证安全性,比如涉及对共享资源的操作。
b 要百分之百地确保线程结東,假设在第二个阶段出现了死循环、阻塞等异常导致无法结 束
c 对资源的释放时间要控制在一个可控的范围之内
2、Two Phase Termination 的示例
计数线程,finally块中体现二阶段关闭设计模式
package com.bjsxt.chapter23.demo01;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class CounterIncrement extends Thread {
// 线程启停标记
private volatile boolean isRunning = true;
// 计数变量
private int counter;
@Override
public void run() {
try{
// 如果是false就结束线程执行业务逻辑
while(isRunning){
TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(1000));
System.out.println(++counter);
//int i = 1 / 0;
}
}catch (Exception e){
// 线程异常关闭的逻辑
// 出现异常,计数线程自己关闭自己
e.printStackTrace();
isRunning=false;
}finally {
// 二阶段处理,清理内存资源等
clean();
System.out.println("二阶段处理完毕.线程真正执行完毕.");
}
}
public void clean(){
System.out.println("二阶段处理中...");
}
// 对外提供关闭线程的方法
public void close(){
// 如果有线程(就是这个线程执行过程中出现异常,标记变量会赋值为false)已经关闭这个线程,那么另一个(main)线程不用再次操作
// 即犹豫设计模式(好处是cpu少操作,省时间,省空间)
if(!isRunning)
return;
isRunning=false;
System.out.println("外部调用关闭方法,线程标记变量为false.");
}
}
Main 测试类
package com.bjsxt.chapter23.demo01;
import java.util.concurrent.TimeUnit;
public class TwoPhaseTerminationTest {
public static void main(String[] args) throws InterruptedException {
CounterIncrement counterIncrement=new CounterIncrement();
counterIncrement.start();
TimeUnit.SECONDS.sleep(3);
counterIncrement.close();
}
}
运行结果:
分析:启停变量赋值为false为外部调用关闭操作。线程执行逻辑中判断如果启停变量是false,就进行二阶段关闭操作。有两个线程,计数线程和main线程。如果技术线程执行逻辑中出现异常,会将启停变量赋值为false,之后main线程调用关闭方法,发现启停变量已经处理为false,会什么也不执行。这里用到了youyu设计模式。
体现:
while(isRunning){
TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(1000));
System.out.println(++counter);
int i = 1 / 0;// 取消注释
}
执行结果:没有“外部调用关闭方法,线程标记变量为false.",因为 技术线程处理了,main线程不在处理。
3、使用 Two Phase Termination 完成通信资源关闭
服务端代码
package com.bjsxt.chapter23.demo02;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 服务器
*/
public class ServerApp extends Thread {
// 关闭启动标记变量
private volatile boolean isRunning=true;
// 端口号
private int port;
// 静态端口(默认端口号)
private final static int DEFAULT_PORT=12722;
// 服务端
private ServerSocket server;
// 客户端集合
private List<ClientHandler> clientHandlers=new ArrayList<>();
// 线程池
private ExecutorService threadPool=Executors.newFixedThreadPool(10);
// 无参构造器,使用静态端口
public ServerApp(){
this(DEFAULT_PORT);
}
// 有参构造器,使用外部传入的整数作为端口
public ServerApp(int port){
this.port=port;
}
// 服务器的业务
@Override
public void run() {
System.out.println("服务端已启动,启动端口为:"+port);
try{
this.server = new ServerSocket(port);
while (isRunning){
// 服务端接受一个客户端
Socket socket = server.accept();
// 将客户端保存到容器中
ClientHandler clientHandler = new ClientHandler(socket);
clientHandlers.add(clientHandler);
// 运行客户端
threadPool.submit(clientHandler);
// 提示
System.out.println(socket.getLocalAddress()+" "+socket.getPort()+" 已成功接入服务端.");
}
}catch (Exception e){
//e.printStackTrace();
isRunning=false;
}finally {
// 二阶段处理
dispose();
System.out.println("服务器二阶段关闭执行完毕.服务器线程真正关闭.");
}
}
private void dispose() {
System.out.println("服务器二阶段关闭将执行...");
// 关闭线程池
threadPool.shutdown();
// 关闭所有客户端
clientHandlers.stream().forEach(ClientHandler::stop);
}
// 对外部提供的关闭服务端的方法,启停变量赋值为false
public void shutdown() throws IOException {
System.out.println("外部关闭服务器将执行");
// 犹豫设计模式
if(!isRunning)
return;
this.isRunning=false;
this.interrupt();// 中断标记,关闭线程
server.close();
System.out.println("外部关闭服务器执行完毕,启停变量赋值为 false.");
}
}
客户端代码
package com.bjsxt.chapter23.demo02;
import java.io.*;
import java.net.Socket;
public class ClientHandler implements Runnable{
// 客户端
private Socket client;
// 启停标记变量
private volatile boolean isRunning=true;
public ClientHandler(Socket client){
this.client=client;
}
@Override
public void run() {
// 这些流资源会自己关闭
try(InputStream is=client.getInputStream();
OutputStream os=client.getOutputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(is));
PrintWriter pw=new PrintWriter(os)){
while (isRunning){
String message=br.readLine();
if(null==message){
break;
}
System.out.println("收到客户端"+client.getPort()+"信息为:"+message);
pw.write("echo message: "+message+"\r\n");
pw.flush();
}
}catch (Exception e){
e.printStackTrace();
isRunning=false;
}finally {
// 二阶段关闭
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("客户端"+client.getPort()+"二阶段关闭操作被执行.真正关闭线程.");
}
}
public void stop() {
if(!isRunning)
return;
this.isRunning=false;
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("外部调用客户端关闭方法,启停变量赋值为 false");
}
}
测试类
package com.bjsxt.chapter23.demo02;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class AppServerTest {
public static void main(String[] args) throws InterruptedException, IOException {
ServerApp serverApp=new ServerApp(12345);
serverApp.start();
TimeUnit.MINUTES.sleep(3);
serverApp.shutdown();
}
}
运行结果:
三分钟时间到,测试程序关闭服务段线程,服务端线程在二阶段执行逻辑中关闭了两个客户端。
总结:
这样写会自动关闭流资源
try(InputStream is=client.getInputStream();
OutputStream os=client.getOutputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(is));
PrintWriter pw=new PrintWriter(os))
{
}
这样写用户如果指定整数端口号,调用有参构造器给port赋值。用户如果不指定,默认使用程序中写死的静态端口,调用的也是有参构造器。调用的语句时this(整数),而不是idea提示的new 类名(整数)
// 端口号
private int port;
// 静态端口(默认端口号)
private final static int DEFAULT_PORT=12722;
// 无参构造器,使用静态端口
public ServerApp(){
this(DEFAULT_PORT);
}
// 有参构造器,使用外部传入的整数作为端口
public ServerApp(int port){
this.port=port;
}
这个是socket自己的端口号,如果是socket.localPort()则是服务段的端口号
socket.getPort()
二阶段关闭设计模式主要是在线程执行逻辑中增加了trycatchfinally块,无论线程正常执行完毕业务逻辑,还是线程执行过程中崩溃(业务逻辑中某语句出现异常),最终都会关闭掉线程中使用的资源,比如socket资源,流资源,还有线程池。