文章目录
IO模型就是说用什么样的通道进行数据的发送和接收,首先要明确一点:IO是操作系统与其他网络进行数据交互,JDK底层并没有实现IO,而是对操作系统内核函数做的一个封装,IO代码进入底层其实都是native形式的。Java共支持3种网络编程IO模式:BIO,NIO,AIO。下文进行介绍
1. BIO
BIO(Blocking IO) 又称同步阻塞IO,一个客户端由一个线程来进行处理,伪代码如下:
可以看到服务端的线程阻塞在两个地方
- 接受客户端请求的
accept()
函数 - 读取数据的
read()
函数
其中读取数据的read()
函数需要经过内核态 - 用户态的切换,read()
的阻塞主要发生在
- 数据从网卡拷贝到内核态
- 数据从内核态拷贝到用户态
这就是传统的阻塞IO,如果客户端一直占用连接不发送数据,那么服务端将一直阻塞在read()
函数上,无法接受其他客户端请求!为了解决这种阻塞,可以每次都创建一个新的进程或线程,去调用 read
函数,这样采用多线程的方式就不用阻塞在原客户端的 read
请求上。
不过,这不叫非阻塞 IO,只不过用了多线程的手段使得主线程没有卡在 read 函数上不往下走罢了。操作系统为我们提供的 read
函数仍然是阻塞的。所以真正的非阻塞 IO,不能是通过我们用户层的小把戏,而是要恳请操作系统为我们提供一个非阻塞的 read
函数。
BIO代码示例
public class SocketServer { public static void main(String[] args) throws IOException { //创建socket连接,端口为9000 ServerSocket serverSocket = new ServerSocket(9000); while (true) { System.out.println("等待连接。。"); //阻塞方法 Socket clientSocket = serverSocket.accept(); System.out.println("有客户端连接了。。");
<span class="token comment">//单线程连接,性能不好,下面开启多线程</span> <span class="token comment">//handler(clientSocket);</span> <span class="token comment">//开启多线程</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Runnable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span> <span class="token function">handler</span><span class="token punctuation">(</span>clientSocket<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">handler</span><span class="token punctuation">(</span><span class="token class-name">Socket</span> clientSocket<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> bytes <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"准备read。。"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//接收客户端的数据,阻塞方法,没有数据可读时就阻塞</span> <span class="token keyword">int</span> read <span class="token operator">=</span> clientSocket<span class="token punctuation">.</span><span class="token function">getInputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>bytes<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"read完毕。。"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>read <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"接收到客户端的数据:"</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>bytes<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> read<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> clientSocket<span class="token punctuation">.</span><span class="token function">getOutputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token string">"HelloClient"</span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> clientSocket<span class="token punctuation">.</span><span class="token function">getOutputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">flush</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
上边是BIO连接的示例代码,启动后可以通过telnet与 localhost 9000 建立连接,并发送字符串信息123,测试结果如下:
测试成功,但BIO现在已经用的不多了,因为它在大并发下有几个致命的缺点:
- 如果BIO使用单线程接受连接,则会阻塞其他连接,效率较低。
- 如果使用多线程虽然减弱了单线程带来的影响,但当有大并发进来时,会导致服务器线程太多,压力太大而崩溃。
- 就算使用线程池,也只能同时允许有限个数的线程进行连接,如果并发量远大于线程池设置的数量,还是与单线程无异
- 操作系统里read函数操作是阻塞操作,如果连接不做数据读写操作会导致线程阻塞,就是说只占用连接,不发送数据,则会浪费资源。比如线程池中500个连接,只有100个是频繁读写的连接,其他占着茅坑不拉屎,浪费资源!
- 另外多线程也会有线程切换带来的消耗
综上所述,BIO方式已经不适用于如下的大并发场景,仅适用于连接数目比较小且固定的架构。这种方式对服务器资源要求比较高,但BIO程序简单易理解。
2. NIO
2.1 操作系统的非阻塞read函数
上边说到,用户层无论是否采用多线程,操作系统的read
函数本质上还是阻塞的,需要从操作系统层面找到一个非阻塞的 read
函数。操作系统提供了这样的功能,只需要在调用 read
前,将文件描述符设置为非阻塞即可(serverSocket.configureBlocking(false);
)。
这个 read
函数的效果是,如果没有数据到达时(到达网卡并拷贝到了内核缓冲区),立刻返回一个错误值(-1),而不是阻塞地等待。这样,就需要用户线程循环调用 read,直到返回值不为 -1,再开始处理业务。流程如下:
注意:
- 数据还未到达网卡,或者到达网卡但还没有拷贝到内核缓冲区之前,这个阶段是非阻塞的。
- 当数据已到达内核缓冲区,此时调用
read
函数仍然是阻塞的,需要等待数据从内核缓冲区拷贝到用户缓冲区,才能返回。
整体流程如下图
2.2 NIO 多路复用
操作系统的read
函数解决了读数据时,数据在 网卡 - 内核态 的阻塞问题。但面临多个客户端连接时,如果为每个客户端创建一个线程,服务器端的线程资源还是很容易被耗光。
面对这种问题,有个聪明的办法就是可以每 accept
一个客户端连接后,将这个文件描述符(connfd)放到一个数组里。然后弄一个新的线程去不断遍历这个数组,调用每一个元素的非阻塞 read
方法,这样,我们就成功用一个线程处理了多个客户端连接。
但这种做法看起来像多路复用(多个连接都用一个线程处理了嘛),其实不然,这种遍历方式也只是我们用户自己想出的小把戏,每次遍历遇到 read
返回 -1 时仍然是一次浪费资源的系统调用(用户态 - 内核态)。真正的多路复用其实是 把一批文件描述符的集合通过一次系统调用传给内核,只在内核态去遍历,用户态仅处理内核态的遍历结果,这样就减少了 用户态- 内核态的请求次数,从根本上解决了这个问题
NIO的多路复用底层主要用的是Linux 内核函数(select,poll,epoll)来实现的。windows不支持epoll实现,windows底层是基于winsock2的select函数实现的(不开源)。三种内核模型的区别如下所示!
select | poll | epoll(jdk 1.5及以上) | |
---|---|---|---|
操作方式 | 遍历 | 遍历 | 回调 |
底层实现数组 | 数组 | 链表 | 哈希表 |
IO效率 | 每次调用都进行线性遍历,时间复杂度为O(n) | 每次调用都进行线性遍历,时间复杂度为O(n) | 事件通知方式,每当有IO事件就绪,系统注册的回调函数就会被调用,时间复杂度O(1) |
最大连接 | 有上限(1024个) | 无上限 | 无上限 |
NIO 有三大核心组件:
- Buffer(缓冲区):buffer 底层就是个数组
- Channel(通道):channel 类似于流,每个 channel 对应一个 buffer缓冲区
- Selector(多路复用器):channel 会注册到 selector 上,由 selector 根据 channel 读写事件的发生将其交由某个空闲的线程处理
注意:NIO 的 Buffer 和 channel 都是既可以读也可以写,NIO的多路复用示意图如下:
应用场景:
- NIO方式适用于连接数目多且连接比较短(轻操作) 的架构, 比如聊天服务器, 弹幕系统, 服务器间通讯,编程比较复杂
2.3 select模型
select
是操作系统提供的系统调用函数,通过它,我们可以把一个文件描述符的数组发给操作系统, 让操作系统去遍历,确定哪个文件描述符可以读写, 然后告诉我们去处理
select
每次都会轮询所有的sockchannel
看下哪个channel
有读写事件,有的话就处理,没有就继续遍历,select和poll模型作为NIO的早期实现,存在一定弊端。下面是一段代码,简单表述一下他们的弊端!
第一个版本:NIO的早期版本模拟代码实现(select)
public class NioServer {
<span class="token comment">// 保存客户端连接</span> <span class="token keyword">static</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">SocketChannel</span><span class="token punctuation">></span></span> channelList <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token generics"><span class="token punctuation"><</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span><span class="token punctuation">,</span> <span class="token class-name">InterruptedException</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 创建NIO ServerSocketChannel,与BIO的serverSocket类似</span> <span class="token class-name">ServerSocketChannel</span> serverSocket <span class="token operator">=</span> <span class="token class-name">ServerSocketChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> serverSocket<span class="token punctuation">.</span><span class="token function">socket</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">InetSocketAddress</span><span class="token punctuation">(</span><span class="token number">9000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 设置ServerSocketChannel为非阻塞, 配置为true,则和BIO类似</span> serverSocket<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"服务启动成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 非阻塞模式accept方法不会阻塞,否则会阻塞</span> <span class="token comment">// NIO的非阻塞是由操作系统内部实现的,底层调用了linux内核的accept函数</span> <span class="token class-name">SocketChannel</span> socketChannel <span class="token operator">=</span> serverSocket<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>socketChannel <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 如果有客户端进行连接</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"连接成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 设置SocketChannel为非阻塞</span> socketChannel<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 保存客户端连接在List中</span> channelList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>socketChannel<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// 遍历连接进行数据读取</span> <span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">SocketChannel</span><span class="token punctuation">></span></span> iterator <span class="token operator">=</span> channelList<span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>iterator<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token class-name">SocketChannel</span> sc <span class="token operator">=</span> iterator<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">ByteBuffer</span> byteBuffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">128</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 非阻塞模式read方法不会阻塞,否则会阻塞</span> <span class="token keyword">int</span> len <span class="token operator">=</span> sc<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 如果有数据,把数据打印出来</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>len <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"接收到消息:"</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">.</span><span class="token function">array</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>len <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 如果客户端断开,把socket从集合中去掉</span> iterator<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"客户端断开连接"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
从上述代码可以看到,NIO使用一个mian线程 + 一个数组 解决了BIO的痛点。具体解决方式如下
- 首先设置服务端连接的阻塞方式为false,代表非阻塞方式
- 非阻塞式接受客户端连接,意味着这段代码一直在轮询的跑,不会阻塞。
- 如果有客户端连接进来,就把这个连接放入list集合中
- 后续遍历list集合,使用内核非阻塞式读取数据
(read函数)
- 读取完成再次轮询跑代码
测试如下:
这种方式虽然解决了BIO的部分痛点,但并不是很完美。因为select
模型的底层实现、io效率、最大连接数在面对高并发时还存在一定弊端!
select
调用需要传入客户端连接的数组,需要拷贝一份到内核,高并发场景下这样的拷贝消耗的资源是惊人的。(epoll
模型可优化为不复制)select
在内核层仍然是通过遍历的方式检查文件描述符的就绪状态,是个同步过程,只不过无系统调用切换上下文的开销。(epoll
模型内核层可优化为异步事件通知)select
仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历。(epoll
模型可优化为只返回给用户就绪的文件描述符,无需用户做无效的遍历)
2.4 poll模型
poll
也是操作系统提供的系统调用函数。它和 select
的主要区别就是,去掉了 select
只能监听 1024
个文件描述符的限制。
2.5 epoll模型
JDK1.5开始引入了epoll基于事件响应机制来优化NIO。epoll 主要就是针对select
模型的三点弊端进行了改进:
- 内核中保存一份文件描述符集合,无需用户每次都重新传入,只需告诉内核修改的部分即可。
- 内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步 IO 事件唤醒。基于事件响应的,类似于观察者模式!
- 内核仅会将有 IO 事件的文件描述符返回给用户,用户也无需遍历整个文件描述符集合。
具体,操作系统提供了这三个函数。
-
第一步,创建一个 epoll 句柄
int epoll_create(int size);
- 1
-
第二步,向内核添加、修改或删除要监控的文件描述符。
int epoll_ctl( int epfd, int op, int fd, struct epoll_event *event);
- 1
- 2
-
第三步,类似发起了 select() 调用
int epoll_wait( int epfd, struct epoll_event *events, int max events, int timeout);
- 1
- 2
内部原理如下:
NIO第二个版本:使用epoll模型后的代码示例
public class NioSelectorServer {
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span><span class="token punctuation">,</span> <span class="token class-name">InterruptedException</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 创建NIO ServerSocketChannel</span> <span class="token class-name">ServerSocketChannel</span> serverSocket <span class="token operator">=</span> <span class="token class-name">ServerSocketChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> serverSocket<span class="token punctuation">.</span><span class="token function">socket</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">InetSocketAddress</span><span class="token punctuation">(</span><span class="token number">9000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 设置ServerSocketChannel为非阻塞</span> serverSocket<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 打开Selector处理Channel,即创建epoll</span> <span class="token class-name">Selector</span> selector <span class="token operator">=</span> <span class="token class-name">Selector</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣</span> serverSocket<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_ACCEPT<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"服务启动成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 阻塞等待需要处理的事件发生</span> selector<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 获取selector中注册的全部事件的 SelectionKey 实例</span> <span class="token class-name">Set</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> selectionKeys <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">selectedKeys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> iterator <span class="token operator">=</span> selectionKeys<span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 遍历SelectionKey对事件进行处理</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>iterator<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token class-name">SelectionKey</span> key <span class="token operator">=</span> iterator<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 如果是OP_ACCEPT事件,则进行连接获取和事件注册</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isAcceptable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token class-name">ServerSocketChannel</span> server <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">ServerSocketChannel</span><span class="token punctuation">)</span> key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">SocketChannel</span> socketChannel <span class="token operator">=</span> server<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> socketChannel<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件</span> socketChannel<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_READ<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"客户端连接成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isReadable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 如果是OP_READ事件,则进行读取和打印</span> <span class="token class-name">SocketChannel</span> socketChannel <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">SocketChannel</span><span class="token punctuation">)</span> key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">ByteBuffer</span> byteBuffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">128</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> len <span class="token operator">=</span> socketChannel<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 如果有数据,把数据打印出来</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>len <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"接收到消息:"</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">.</span><span class="token function">array</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>len <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 如果客户端断开连接,关闭Socket</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"客户端断开连接"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> socketChannel<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">//从事件集合里删除本次处理的key,防止下次select重复处理</span> iterator<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
在使用epoll模型之后,对简单版本的NIO做了优化处理,可以理解为在第一个版本的NIO上,又增加了一个就绪事件列表集合,这个集合中存放着有事件响应的连接,然后开启一个线程去监听这个集合,有元素的话就进行处理。
总结:
- 一切的开始,都起源于这个
read
函数是操作系统提供的,而且是阻塞的,我们叫它阻塞 IO
。为了破这个局,程序员在用户态通过多线程来防止主线程卡死。 - 后来操作系统发现这个需求比较大,于是在操作系统层面提供了非阻塞的
read
函数,这样程序员就可以在用户态用一个线程内通过while
遍历,完成多个文件描述符的读取(如果read
函数未就绪直接返回-1),这就是非阻塞 IO
。 - 但多个文件描述符的读取就需要遍历,当高并发场景越来越多时,用户态遍历的文件描述符也越来越多,相当于在 while 循环里进行了越来越多的系统调用。后来操作系统又发现这个场景需求量较大,于是又在操作系统层面提供了这样的遍历文件描述符的机制,这就是
IO 多路复用
。 - 多路复用快的原因在于:把遍历操作强加到内核中,使得原来的
while
循环里多次系统调用,变成了一次系统调用 + 内核层遍历这些文件描述符。 - 多路复用有三个函数,最开始是
select
,然后又发明了poll
解决了select
文件描述符的限制,然后又发明了epoll
解决select
的三个不足。
2.6 深入hotpost源码分析epoll模型
NIO第二个版本的代码中,与简单版本最主要的代码区别在以下三行代码
// 1. 打开Selector处理Channel,即创建epoll ,创建多路复用器
Selector selector = Selector.open();
// 2. 把ServerSocketChannel链接注册到selector多路复用器上,并且selector对客户端accept连接操作感兴趣
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
// 3. 阻塞等待需要处理的事件发生
selector.select();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这三行代码内部是优化的核心,下面一一介绍!
2.3.1. Selector selector = Selector.open();
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
= SelectorProvider.provider() ===
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">SelectorProvider</span> <span class="token function">provider</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
<span class="token keyword">synchronized</span> <span class="token punctuation">(</span>lock<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>provider <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> provider<span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token class-name">AccessController</span><span class="token punctuation">.</span><span class="token function">doPrivileged</span><span class="token punctuation">(</span>
<span class="token keyword">new</span> <span class="token class-name">PrivilegedAction</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">SelectorProvider</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
<span class="token keyword">public</span> <span class="token class-name">SelectorProvider</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">loadProviderFromProperty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> provider<span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">loadProviderAsService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> provider<span class="token punctuation">;</span>
<span class="token comment">// 我们在Windows环境下,默认创建的是WindowsSelectorProvider,如下所示</span>
provider <span class="token operator">=</span> <span class="token class-name"><span class="token namespace">sun<span class="token punctuation">.</span>nio<span class="token punctuation">.</span>ch<span class="token punctuation">.</span></span>DefaultSelectorProvider</span><span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> provider<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
= 默认创建的是WindowsSelectorProvider =
public static SelectorProvider create() {
return new WindowsSelectorProvider();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
进入open方法源码中,可以看到创建的是WindowsSelectorProvider,而项目发布一般都是在linux上,所以需要进入hotport源码中看一下Linux平台的实现。这中现象也是JVM的跨平台性的体现。进入hotpost源码发现如果是linux环境,则创建EpollSelectorProvider。也发现Linux上实现是基于Epoll模型!
所以 SelectorProvider.provider().openSelector() 其实是EpollSelectorProvider.openSelector(),代码如下所示!
可以看到 EPollSelectorImpl 是具体的实现,那么在EPollSelectorImpl实例化时做了什么事情呢?
在这个 EPollArray 包装类中调用了一个 epoll_create 的本地方法,创建了一个epoll对象,并返回一个非负数作为文件描述符,用于对epoll接口的所有后续调用。这个方法属于linux操作系统内核中的方法。至此Selector.open()方法解释完毕,总的来说就是创建了一个linux内核层面的Epoll对象!
2.3.2. serverSocket.register(selector, SelectionKey.OP_ACCEPT)
查看这个方法的源码,同样要使用LInux下的平台中的EpollSelectorProvider的register方法,核心代码如下:
这个方法是把当前连接注册到第一步EPollSelectorImpl实例化时生成的EPollArray的集合中,此时并没有事件驱动。直到 selector.select()方法调用linux内核函数epoll_ctl
2.3.3. selector.select()
这个select方法内部会调用doselect()方法,这个方法同样要使用LInux下的平台中的EpollSelectorProvider
在这个poll方法中,分别调用了Linux核心函数 epoll_ctl 和 epoll_wait,下面分别解释其含义
- epoll_ctl: 负责真正去关联socketChannel 和 epoll实例(selector)的关系。如果客户端连接(socketChannel)有发送事件,就会把存在于EPollArray中的这个连接放入epoll实例中的就绪事件列表(rdlist)中去。
其中这个放入就绪事件列表的操作是怎么做的呢?
放入操作并非是java去做,每当服务端收到客户端的连接,且该链接有响应事件时,就是使用操作系统的硬中断功能把该连接放入就绪事件列表中去。后续遍历就绪事件列表中的元素即可,减少了无效遍历。 - epoll_wait:监听就绪事件列表中的元素,如果没有数据则阻塞。如果有数据,则把数据放入selectedKeys中,供java代码获取元素,响应事件!
2.7 NIO与redis
Redis就是典型的基于epoll的NIO线程模型(nginx也是),epoll实例收集所有事件(连接与读写事件),把有数据交互的连接放入就绪事件列表中,由一个服务端线程连续处理所有就绪事件列表中的命令。
在ae_epoll.c源码中也发现有 epoll_create、epoll_ctl 和 epoll_wait,原理与上文讲的类似
问题一:为什么 redis 不建议用 bigkey?
bigkey的big体现在单个value值很大,一般认为超过10KB就是bigkey。由于redis底层用的是NIO,多路复用一个线程,如果存在bigkey的话,这个bigkey就会占用这个线程较大的时间,导致其他连接的数据交互阻塞,所以不建议使用bigkey。注意:这里说的阻塞并不是 异步非阻塞的阻塞。
3. AIO
AIO自JDK1.7以后才开始支持,是异步非阻塞的,客户端与服务端的连接(accept)、数据读写(read、write)不再由main线程去执行,而是开辟一个回调函数,当客户端与服务端建立连接时,把这个客户端的连接传入回调函数中,由服务端启动一个子线程去处理,这就完成了异步操作!适用于连接数较多且连接时间较长的应用。
AIO与BIO、NIO的不同之处在于:
- AIO是 异步非阻塞模型
- NIO是 同步非阻塞模型
- BIO是 同步阻塞模型
AIO代码示例
public class AIOServer {
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">//创建服务端</span> <span class="token keyword">final</span> <span class="token class-name">AsynchronousServerSocketChannel</span> serverChannel <span class="token operator">=</span> <span class="token class-name">AsynchronousServerSocketChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">InetSocketAddress</span><span class="token punctuation">(</span><span class="token number">9000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//使用CompletionHandler异步处理客户端连接</span> serverChannel<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">CompletionHandler</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">AsynchronousSocketChannel</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">completed</span><span class="token punctuation">(</span><span class="token class-name">AsynchronousSocketChannel</span> socketChannel<span class="token punctuation">,</span> <span class="token class-name">Object</span> attachment<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"2--"</span><span class="token operator">+</span><span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 在此接收客户端连接,如果不写这行代码后面的客户端连接连不上服务端</span> serverChannel<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span>attachment<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>socketChannel<span class="token punctuation">.</span><span class="token function">getRemoteAddress</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">ByteBuffer</span> buffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">1024</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//使用CompletionHandler异步读取数据</span> socketChannel<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>buffer<span class="token punctuation">,</span> buffer<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">CompletionHandler</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">Integer</span><span class="token punctuation">,</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">completed</span><span class="token punctuation">(</span><span class="token class-name">Integer</span> result<span class="token punctuation">,</span> <span class="token class-name">ByteBuffer</span> buffer<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"3--"</span><span class="token operator">+</span><span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> buffer<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>buffer<span class="token punctuation">.</span><span class="token function">array</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> result<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> socketChannel<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">wrap</span><span class="token punctuation">(</span><span class="token string">"HelloClient"</span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">failed</span><span class="token punctuation">(</span><span class="token class-name">Throwable</span> exc<span class="token punctuation">,</span> <span class="token class-name">ByteBuffer</span> buffer<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> exc<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">failed</span><span class="token punctuation">(</span><span class="token class-name">Throwable</span> exc<span class="token punctuation">,</span> <span class="token class-name">Object</span> attachment<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> exc<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"1--"</span><span class="token operator">+</span><span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token class-name">Integer</span><span class="token punctuation">.</span>MAX_VALUE<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
AIO作为异步非阻塞模型,理论上来说应该被广泛使用,但大多数公司并没有使用AIO,而是使用了netty,为什么?
- 首先AIO得底层实现仍使用Epoll,并没有很好的实现异步,在性能上对比NIO没有太大优势
- 其次AIO的代码逻辑比较复杂,且Linux上AIO还不够成熟
- Netty在NIO上做了很多异步的封装,是异步非阻塞框架
4. BIO、NIO、AIO的对比
一个关于同步异步与阻塞非阻塞的段子:
老张爱喝茶,废话不说,煮开水。
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
- 老张把水壶放到火上,立等水开。(同步阻塞) 老张觉得自己有点傻
- 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞) 老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
- 老张把响水壶放到火上,立等水开。(异步阻塞)老张觉得这样傻等意义不大
- 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)老张觉得自己聪明了。
所谓同步异步,只是对于水壶而言。
- 普通水壶,同步
- 响水壶,异步。
- 虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。
同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。
所谓阻塞非阻塞,仅仅对于老张而言。
- 立等的老张,阻塞
- 看电视的老张,非阻塞。