NIO入门
伪异步I/O
1.伪异步I/O的诞生和定义
首先伪异步I/O是为了解决BIO中的一个客户端就要服务端一个线程处理的问题而诞生的。
伪异步I/O是通过线程池和任务队列来实现的,无论有多少客户端,线程池都可以灵活的调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽,当有新的客户端接入时,将客户端的socket封装成一个Task(该任务实现Runnable接口)投递到后端的线程池中处理。
- 伪异步I/O创建的TimeServer源码
package com.carfi.netty.fakeio;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
1. @author: ll
2. @time: 2020/6/14 23:15
*/
public class TimeServer {
public static void main(String[] args) throws IOException {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
}
}
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
System.out.println("Thr rime server is start in port : " + port);
Socket socket = null;
//线程池
TimeServerHandlerExecutePool timeServerHandlerExecutePool = new TimeServerHandlerExecutePool(50, 10000);
while (true) {
socket = serverSocket.accept();
//线程池执行处理客户端线程任务
timeServerHandlerExecutePool.execute(new TimtServerHandler(socket));
}
} finally {
if (serverSocket != null) {
System.out.println("The time server close");
serverSocket.close();
serverSocket = null;
}
}
}
}
其中客户端处理代码由线程池完成,将请求socket封装为task,调用线程池execute方法执行,避免每个请求都创建一个新的线程
3. 伪异步I/O创建的TimeServerHandlerExecutePool源码
package com.carfi.netty.fakeio;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 服务端处理客户端连接控制线程池
* @author: ll
* @time: 2020/6/14 23:20
*/
public class TimeServerHandlerExecutePool {
private ExecutorService executorService;
/**
*
* @param maxPollSize 最大线程数量
* @param queueSize 最大阻塞队列数量
*/
public TimeServerHandlerExecutePool(int maxPollSize, int queueSize) {
/**
* Runtime.getRuntime().availableProcessors() 用来设置该线程池核心线程数量
* maxPollSize 用来设置该线程池最大线程数量
* 120L 线程池中超过corePoolSize数目的空闲线程最大存活时间
* TimeUnit.SECONDS 时间单位
* new ArrayBlockingQueue<Runnable>(queueSize) 阻塞任务队列,当线程池达到corePoolSize时,新提交任务将被放入阻塞任务队列中,等待线程池中任务调度执行
* 当阻塞任务队列已满,且最大线程池大小>核心线程数量时(此时workQueue已满),新提交任务会创建新线程执行任务
*/
executorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
maxPollSize, 120L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize));
}
public void execute(Runnable task) {
executorService.execute(task);
}
public static void main(String[] args) {
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
- 伪异步I/O创建的TimtServerHandler源码
package com.carfi.netty.fakeio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Date;
/**
* @author: ll
* @time: 2020/6/11 22:56
*/
public class TimtServerHandler implements Runnable {
private Socket socket;
public TimtServerHandler() {
}
public TimtServerHandler(Socket socket) {
this.socket = socket;
}
@Override
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 = null;
String body = null;
while (true) {
body = in.readLine();
if (body == null) {
break;
}
System.out.println("The time server receive order : " + body);
currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
out.println(currentTime);
}
} catch (Exception e) {
if (in != null) {
try {
in.close();
} catch (IOException ex) {
ex.printStackTrace();
}
in = null;
}
if (out != null) {
out.close();
out = null;
}
if (this.socket != null) {
try {
this.socket.close();
} catch (IOException ex) {
ex.printStackTrace();
}
this.socket = null;
}
}
}
}
- 伪异步I/O创建的TimeClient源码
package com.carfi.netty.fakeio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* @author: ll
* @time: 2020/6/11 23:26
*/
public class TimeClient {
public static void main(String[] args) {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
//采用默认值
}
}
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket("127.0.0.1", port);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
out.println("QUERY TIME ORDER");
System.out.println("Send order 2 server succedd.");
String resp = in.readLine();
System.out.println("Now is : " + resp);
} catch (IOException e) {
//不需处理
} finally {
if (out != null) {
out.close();
out = null;
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
socket = null;
}
}
}
}
伪异步I/O的优点:
- 由于线程池和消息队列是有界的,无论客户端并发连接数有多大,都不会导致线程个数膨胀或内存溢出
伪异步I/O的缺点:
- 伪异步底层依然是同步阻塞模型
- 对Socket输入流读取时,会一直阻塞,直到 有数据可读,可用数据已经读取完毕,发生空指针异常或者I/O异常
- 当对方发送请求或者应答消息比较缓慢或者网络延迟,读取输入流乙方的通信线程也将会被长时间堵塞,如果堆放要60s才能够将数据发送完成,那么读取一方的线程也将会被同步阻塞60s,其它接入消息只能在阻塞队列中排队
- 当调用OutputStream的write方法写输出流的时候,也将被堵塞,直到所有要发送的字节全部写入完毕,或者发生异常,如果消息接收方处理缓慢,同步阻塞I/O将会导致write方法被无限期堵塞
伪异步I/O由于对方应答时间过长可能引起的级联故障:
- 服务端也将延长应答时间
- 采用伪异步I/O的线程正在读取故障服务节点的响应,由于读取输入流是阻塞的,也将被同步阻塞
- 假如所有的可用线程都被故障服务器堵塞,那后续的所有I/O消息都将在队列中排队
- 由于线程池采取阻塞队列实现,当队列积满之后,后续入队列的操作将被阻塞
- 新的客户端请求将被拒绝,发生超时连接
- 如果所有的连接全部超时,可认为系统已崩溃,无法接受新的请求消息