本文不打算讲解BIO怎么用?NIO如何用?本文重点是NIO底层原理。
本文打算从以下几个方面讲解:
1、BIO通讯模型(网络方面)是什么?
2、NIO通讯模型(网络方面)是什么?解决了什么问题?
1. BIO通讯模型
模型解释:
BIO场景下,客户端(Client)发起连接请求,服务端接收到请求后,会分配一个业务线程处理这次访问,执行业务处理,写入响应流。
无论是服务端还是客户端,数据的读写都是阻塞的。比如,服务端收到客户端的请求,想要获取客户端传过来的请求参数,就会执行读操作,此时,如果由于网络原因导致客户端写入数据慢或者服务端接收数据慢,这个过程会非常耗时,此时应用线程就只能阻塞等待数据可读,这个过程会很浪费CPU资源的。并且,随着用户请求的增多,阻塞队列满了,而应用线程没有释放,就会导致后来的请求被抛弃,得不到处理。
为什么会出现这种情况?究其原因,服务端执行读数据的操作,本质上是CPU向操作系统内核发出一条指令,让操作系统通过TCP/IP协议,从网络读取数据到内核,再从内核到内存中。CPU执行指令速度非常快,而操作系统执行IO的速度远远赶不上CPU执行指令的速度,就会导致CPU时间的浪费。
2.NIO通讯模型
模型解释:
NIO场景下,客户端(Client)发起请求,服务端接收请求后,并不是直接分配业务线程处理这次请求,而是交给专门的IO线程(JAVA 中的Selector)读取请求流,当数据准备好以后,才会交给业务线程执行业务逻辑,最后交给IO线程写入响应流。
到这里,读者可能会有两个疑问?NIO模型下,IO线程会成为瓶颈?NIO解决了什么问题(与BIO相比)?
IO线程会成为瓶颈吗?这个问题得从IO线程的底层实现说起,NIO之所以是同步非阻塞,就是因为底层操作系统支持同步非阻塞,JVM只是通过系统调用本地方法实现同步非阻塞的(本质上是操作系统实现同步非阻塞,而JVM只是通过本地方法执行系统调用而已)。linux系统提供了epoll系统调用,epoll是基于事件驱动方式来实现的(也就是说,底层操作系统准备好了数据,以事件驱动的机制回调通知),而NIO中的Selector的select()方法调用,是通过本地方调用epoll系统调用来实现非阻塞的,最大限度利CPU时间片,所以IO线程的瓶颈也就是硬件瓶颈。
NIO解决了什么问题?
通过单独的IO线程,当有可读、可写的事件发生的时候再去做读写操作,这个时候就不用像BIO那样一直阻塞等待在那,业务线程就可以被释放出来做更多的事情。说白了,提高了CPU利用率,让更少的线程做更多的事。