1 什么是Two Phase Termination模式
当一个线程正常结束,或者因被打断而结束,或者因出现异常而结束时,我们需要考虑如何同时释放线程中资源,比如文件句柄、socket套接字句柄、数据库连接等比较稀缺的资源
当希望结束这个线程时,发出线程结束请求,接下来线程不会立即结束,而是会执行相应的资源释放动作直到真正的结束,在终止处理状态时,线程虽然还在运行,但是进行的是终止处理工作,因此终止处理又称为线程结束的第二个阶段,而受理终止要求则被称为线程结束的第一个阶段。
在进行线程两阶段终结的时候需要考虑如下几个问题:
- 第二阶段的终止保证安全性,比如涉及对共享资源的操作。
- 要百分之百地确保线程结束,假设在第二个阶段出现了死循环、阻塞等异常导致无法结束。
- 对资源的释放时间要控制在一个可控的范围之内。
Two Phase Termination与其说是一个模式,还不如说是线程使用的一个技巧。其主要针对的是当线程结束生命周期时,能有机会做一些资源释放的动作
2 Two Phase Termination设计模式示例
之前的一个服务端处理代码:
public class ClientHandler implements Runnable {
private final Socket socket;
private final String clientIdentify;
public ClientHandler(final Socket socket) {
this.socket = socket;
this.clientIdentify = socket.getInetAddress().getHostAddress() + ":" + socket.getPort();
}
@Override
public void run() {
try {
this.chat();
} catch (IOException e) {
e.printStackTrace();
}
}
private void chat() throws IOException {
BufferedReader bufferedReader = wrap2Reader(this.socket.getInputStream());
PrintStream printStream = wrap2Print(this.socket.getOutputStream());
String received;
while ((received = bufferedReader.readLine()) != null) {
// 将客户端发送的消息输出到控制台
System.out.printf("client:%s-message:%s\n", clientIdentify, received);
if (received.equals("quit")) {
// 如果收到的是quit,就退出
write2Client(printStream, "client will close");
socket.close();
break;
}
// 响应结果给客户端
write2Client(printStream, "Server:" + received);
}
}
/**
* 将输入字节流封装成BufferedReader缓冲字符流
* @param inputStream
* @return
*/
private BufferedReader wrap2Reader(InputStream inputStream) {
return new BufferedReader(new InputStreamReader(inputStream));
}
/**
* 将输出字节流封装成PrintStream
* @param outputStream
* @return
*/
private PrintStream wrap2Print(OutputStream outputStream) {
return new PrintStream(outputStream);
}
private void write2Client(PrintStream print, String message) {
print.println(message);
print.flush();
}
}
当客户端发送quit指令时,服务端会断开与客户端的连接“socket.close();”,如果客户端发送正常信息后发生异常,chat方法会抛出错误,那么此时该线程的任务执行也将结束,socket和thread一样都属于严重依赖操作系统资源的对象,各个操作系统中可供创建的线程数量有限,为了能够确保客户端即使异常关闭,我们也能够尽快地释放socket资源,two phase Termination设计模式将是一种比较好的解决方案
接下来就改进一一下:
@Override
public void run() {
try {
this.chat();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 释放资源
this.release();
}
}
private void release() {
try {
if (this.socket != null) {
socket.close();
}
} catch (Throwable e) {
// ignore
}
}
当chat方法出现异常会被run方法捕获,在run方法中加入finally子句,用于执行客户端socket的主动关闭,为了使客户端的关闭不影响线程任务的结束,我们捕获了Throwable异常(在关闭客户端连接时出现的异常,视为不可恢复的异常,基本上没有针对该异常进行处理的必要)。