先放上一个B站的视频:NIO的原理
一
说NIO就必须要了解下BIO,Java首先提供了BIO,为了优化BIO的性能短板,加入了NIO,搞清楚了BIO的短板,也就知道了为什么要有NIO。
BIO的B是blocking即阻塞,即在IO的过程中某些操作会导致线程阻塞,以Socket API为例,Server端:
ServerSocket ss = new ServerSocket();
InetSocketAddress ia = new InetSocketAddress("localhost", 8888);
ss.bind(ia);
while (true) {
Socket socket = ss.accept();
InputStream inputStream = socket.getInputStream();
int read = inputStream.read();
if (read == 0) {
System.out.println("no data...");
} else {
System.out.println("has data...");
}
}
上面这段代码有两个阻塞处:
Socket socket = ss.accept();
和
int read = inputStream.read();
闭上眼睛,想象一下,你是一台服务器,执行到了 accept(),相当于站在家门口等待客人的到来,如果客人一直不来,你就要一直等在这里,你干不了其他事情。
等了半天,终于来了一个客人,你把它接入客厅,然后又开始等待,等待客人说话,客人不说话,你不知道该干什么,直到等到了客人说话,你才能干其他事情。
想象一下,如果客人一直不说话,而门口又来了很多客人需要你接待,就导致了服务器响应缓慢。
在想一下,如果客厅有了很多客人,而此时你恰好在门口等待客人,由于不等到客人就不能干其他事情,哪怕已经在客厅的客人有话要说,你也无暇顾及,你必须等到新客人才行。
好傻逼的主人。
BIO就是这样干的。
那NIO没出来之前,服务器不是都死慢死慢的?
那也不至于,刚刚举得例子是单线程的例子,主人既要迎宾又要陪客,的确难为他了。于是主人拿钱雇几个人去客厅帮忙接待客人,主人就可以专心致志的在门口迎接客人了。每次一个新客人到了之后,主人就分配一个雇员一对一接待,此时主人就得心应手的多了。
这时会有另一个问题,主人的财力有限,不能无限雇人,毕竟一个客人要一个员工去接待,很多客人其实么那么多要求,所以很多雇员绝大多数时间都很闲无事可干。
时间一长,主人觉得不行,客厅就那么大,站这么多雇员在里面,能接待接待客人的空间就少了。
这种情况下,BIO就搞不定了。
在实际的应用场景中,如果一个连接占用一个线程,会造成极大的浪费,按照28原则,80%的连接是不活跃的,80%的时间是不活跃的,这就意味着浪费了很多的资源。
NIO就是来解决这个问题的。
上面那个例子中,不要使用一个员工陪一个客户,让员工处理完一个客户的请求后,如果客户没有其他请求,则员工去看看其他的客人有没有什么要求,这样一个员工可以依次处理客人的需求。增加了客人的容量,同时使员工的工作更加饱和,不浪费员工的才华。
二
回过头再去想想,在没有雇员时,即不用多线程,BIO的场景有没有可以优化的余地,即主人既要迎宾又要待客时。即单线程下,如何优化?
先看一段伪代码:
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket();
InetSocketAddress ia = new InetSocketAddress("localhost", 8888);
ss.bind(ia);
// 伪代码:设置accept不阻塞
ss.setBlocking(false);
List<Socket> sockets = new ArrayList<>();
while (true) {
// 此处不再阻塞
Socket socket = ss.accept();
InputStream inputStream = socket.getInputStream();
if (socket == null) {
System.out.println("no data...");
for (Socket client : sockets) {
// 此处不再阻塞
int read = inputStream.read();
}
} else {
// 伪代码:设置read()不阻塞
socket.setBlocking(false);
sockets.add(socket);
System.out.println("has data...");
for (Socket client : sockets) {
// 此处不再阻塞
int read = inputStream.read();
}
}
}
}
将原本阻塞的两个API设置为不阻塞,即主人先去门口迎客,没有客人时,并不做过多等待,而是跑会客厅接待客人,如果客人暂时没有要求,主人并不会傻等这个客人,而是马上去询问下一个客人,如果循环往复。这样主人能够做的事情就多了,资源得到充分利用,只不过主人的压力也就是随之升高。
三
NIO来了。