为了解决同步阻塞IO面临的一个请求需要一个线程处理的问题,后来有人对它的线程模型进行了优化,通过一个线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远大于N。通过线程池可以灵活的调配线程资源,设置线程的最大值,防止由于海量请求并发接入导致线程耗尽。
下面我们通过一个示例来说明
服务端代码
TimeServer
public class TimeServer {
public static void main(String[] args) throws IOException {
ServerSocket server = null;
try {
// 如果端口未被占用且合法则创建成功
server = new ServerSocket(8080);
Socket socket = null;
// 创建线程池
TimeServerHandlerExecutePool executePool = new TimeServerHandlerExecutePool(50,10000);
// 通过无限循环来监听客户端连接,如果没有客户端接入,主线程阻塞在accept操作上
while (true) {
socket = server.accept();
// 将请求任务丢到线程池中进行处理
executePool.execute(new TimeServerHandler(socket));
}
} catch (Exception e) {
System.out.println("创建serverSocket失败,端口:" + 8080);
e.printStackTrace();
} finally {
System.out.println("The time server close");
if (server != null) {
server.close();
}
}
}
}
服务端创建一个ServerSocket,然后通过无限循环的方式来监听客户端连接,如果没有客户端接入,则主线程会阻塞在accept操作上。定义一个线程池用来处理客户端请求任务,当有客户端连接接入后,将socket组装成TimeServerHandler,这是个Runnable,丢到线程池中进行处理
TimeServerHandler
public class TimeServerHandler implements Runnable {
private Socket socket;
public TimeServerHandler(Socket socket) {
this.socket = socket;
}
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(), true);
String currentTime;
String body;
while (true) {
// ★ 读取一行,如果读到输入流尾部,则返回值为null,退出循环
body = in.readLine();
if (null == body) {
break;
}
System.out.println("The time server receive order : " + body);
// 如果读到了非空值,对内容进行判断,如果请求消息为查询时间的指令 QUERY TIME ORDER,则获取当前系统时间
// 通过PrintWriter的println函数发送给客户端,然后退出循环
if ("QUERY TIME ORDER".equalsIgnoreCase(body)) {
currentTime = new Date(System.currentTimeMillis()).toString();
} else {
currentTime = "BAD ORDER";
}
// ★ 通过printWriter发送给客户端
out.println(currentTime);
}
} catch (IOException e) {
System.out.println("exception");
e.printStackTrace();
// 释放输入流
if (in != null) {
try {
in.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
// 释放输出流
if (out != null) {
out.close();
}
// 释放socket套接字句柄资源
if (this.socket != null) {
try {
this.socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
TimeServerHandlerExecutePool
public class TimeServerHandlerExecutePool {
private ExecutorService executorService;
public TimeServerHandlerExecutePool(int maxPoolSize, int queueSize) {
this.executorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), maxPoolSize,
120L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize));
}
public void execute(Runnable task) {
this.executorService.execute(task);
}
}
客户端代码
TimeClient
public class TimeClient {
public static void main(String[] args) {
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
//
socket = new Socket("127.0.0.1", 8080);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
// ★ 通过PrintWriter向服务端发送 QUERY TIME ORDER 指令
out.println("QUERY TIME ORDER");
System.out.println("send order to server succeed.");
// ★ 通过BufferedReader读取响应并打印
String response = in.readLine();
System.out.println("Now is : " + response);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 释放输入流
if (in != null) {
try {
in.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
// 释放输出流
if (out != null) {
out.close();
}
// 释放socket套接字句柄资源
if (socket != null) {
try {
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
分别执行服务端和客户端,执行结果如下:
服务端执行结果:
The time server receive order : QUERY TIME ORDER
客户端执行结果:
send order to server succeed.
Now is : Sun Jul 19 16:48:30 CST 2020
到此伪异步IO的示例程序讲解完毕
Talk is cheap , show me the picture
最后通过一张图来说明伪异步IO的通信模型
总结
由于线程池是有界的,无论客户端并发连接数多大,都不会导致线程个数过于膨胀或者内存溢出,相比传统的BIO是一种改良。但仍然无法解决同步IO导致的通信线程阻塞问题
参考资料
- 《Netty权威指南》第二版 by 李林锋