多线程设计模式——二十三、Two Phase Termination 设计模式

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资源,流资源,还有线程池。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值