上次说了BIO编程。为了解决同步阻塞I/O面临的一个链路需要一个线程处理的问题,后来人们对它的线程模型进行了优化,服务器端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M对应线程池N的比例关系。通过线程池可以灵活调配线程资源,设置线程的最大值,防止由于海量并发客户端接入导致线程耗尽。这就是伪异步I/O编程。
如下图所示,采用线程池和任务队列可以实现伪异步I/O通信框架。
图片伪异步I/O服务器端通信模型服务器端代码:
package com.test.aio1;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 伪异步I/O服务器端
* @author 程就人生
* @Date
*/
public class HelloServer {
public static void main( String[] args ){
int port = 8080;
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
Socket socket = null;
HelloServerHandleExecutePool execute = new HelloServerHandleExecutePool(50, 1000);
// 通过无限循环监听客户端连接
while(true){
// 如果没有客户端连接,则阻塞在Accept操作上
socket = serverSocket.accept();
// 如果有客户端连接,则加入线程池执行
execute.execute(new HelloHandler(socket));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 线程池类
* @author 程就人生
* @Date
*/
class HelloServerHandleExecutePool{
private ExecutorService executorService;
public HelloServerHandleExecutePool(int maxPoolSize, int queueSize) {
executorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), maxPoolSize, 120L, TimeUnit.SECONDS, new ArrayBlockingQueue<java.lang.Runnable>(queueSize));
}
public void execute(java.lang.Runnable task){
executorService.execute(task);
}
}
/**
* hello处理类
* @author 程就人生
* @Date
*/
class HelloHandler implements Runnable{
private Socket socket;
public HelloHandler(Socket socket) {
this.socket = socket;
}
public void run() {
BufferedReader reader = null;
PrintWriter writer = null;
try {
reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
// 返回给客户端,是否刷新设置为true
writer = new PrintWriter(this.socket.getOutputStream(), true);
String body = null;
while(true){
body = reader.readLine();
if(body == null){
break;
}
System.out.println("服务器端接收到的:" + body);
writer.println("来自服务器端的响应!");
}
} catch (IOException e) {
e.printStackTrace();
// 出现异常时,对资源的释放
if(reader != null){
try {
reader.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if(writer != null){
writer.close();
writer = null;
}
if(socket != null){
try {
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
伪异步I/O服务器端的主函数有了变化,先创建一个Hello服务处理类的线程池。当接收到新的客户端连接时,将请求soket封装成一个Task,然后调用线程池的execute方法执行,从而避免每个客户端连接时都创建一个新线程。由于线程池和消息队列都是有界的。因此,无论客户端并发连接数多大,都不会导致服务器端线程个数过于膨胀而导致内存溢出,相比于传统的BIO线程模型是一种改良。 运行服务器端结果。
图片
运行客户端结果
虽然伪异步I/O通信框架采用了线程池实现,因此避免了每个请求都创建一个独立线程造成的线程资源耗尽问题,但它的底层的通信依旧采用同步阻塞模型,因此无法从根本上解决问题。一起看下socket的InputStream方法。
图片
图片
再看下InputStream类。
图片
当对Socket的输入流进行读取操作时,它会一直堵塞下去,直到发生三种事件:
- 有数据可读。
- 可用数据已经读完。
- 发生异常。
如果对方发生请求或者应答比较缓慢,或者网络传输比较慢,读取输入流一方的通信线程将会被长时间阻塞。如果对方要60s才能将数据发送完成,读取一方的I/O也会被阻塞60s。在这60s期间,其他接入的客户端只能在消息队列中排队。伪异步I/O只是对BIO线程模型的一个简单优化,无法从根本上解决同步IO导致的通信线程阻塞问题。
伪异步I/O的缺点显而易见。
- 如果客户端请求量增加,会导致服务器端处理应答消息缓慢。
- 如果有故障节点,由于读取输入流的阻塞的,它会被阻塞,直到释放。
- 如果所有的线程都被故障服务器阻塞,那么后续的IO消息都将在队列中排队等等。
网友评论