Java IO模型
网络IO通信过程,网络数据读取,会设计两个对象,一个是调用这个IO操作的用户线程,一个是操作系统内核。
一个进程的地址空间分为用户空间和内核空间,用户线程不能直接访问内核空间。
用户线程发起IO操作后,网络数据读取经历两个步骤:
- 用户线程等待将内核数据从网卡拷贝到内核空间;
- 内核将数据从内核空间拷贝到用户空间。
IO模型的区别在于:准备数据、拷贝数据,实现这两个步骤的方式不一样。
同步阻塞IO
用户线程read调用后就阻塞,让出CPU;内核等网卡数据到来,把数据同网卡拷贝到内核空间,接着把数据拷贝到用户空间,再把用户线程唤醒。
同步非阻塞IO
用户线程不断的发起read调用,数据没到内核空间时,每次都返回失败,直到数据到了内核空间,这一次调用read,但在等待数据从内核空间拷贝到用户空间这段时间,线程都还是阻塞的,等到数据到了用户空间再把线程唤醒。
IO多路复用
用户线程操作分成了两步,线程先发起select调用,目的是问内核数据准备好了吗。等内核数据准备好之后,用户线程再发起read调用。
在等待数据从内核空间拷贝到用户空间这段时间离,线程还是阻塞的。
一次select调用可以向内核查询多个数据通道(channel)的状态,所以叫多路复用。
异步IO
用户线程发起read调用的同时注册一个回调函数,read立即返回,等内核将数据准备好后,再调用指定的回调函数完成处理。在这个过程中,用户线程一直没有阻塞。
NIO.2
NIO和NIO.2最大的区别就是,一个是同步,一个是异步。 异步,应用程序不需要自己去触发数据从内核空间到用户空间的拷贝。
异步IO模型,内核做了很多工作,把数据准备好,并拷贝到内核空间,再通知应用程序去处理,然后调用应用程序注册的回调函数。
应用程序在调用read API时告诉内核两件事,数据准备好了以后拷贝到哪个buffer,以及回调哪个回调函数去处理这些数据。 之后,内核接到这个read命令后,等待网卡数据到达,数据到了之后,内核在中断程序把数据从网卡拷贝到内核空间,接着做TCP/IP协议层面的数据解包和重组,再把数据拷贝到应用程序指定buffer,最后回调应用程序指定的回调函数。
异步模式下,内核忙前忙后,最大限度的提高了IO通信效率。
APR
Acceptor本质监听连接、接收并建立连接,操作系统4个接口,socket、bind、listen、accept。
NioEndpoint
java的多路复用,两步:
- 创建一个selector,在他身上注册各种感兴趣的事件,然后调用select方法,等事件发生。
- 事情发生后,如果可以读,就创建一个新的线程从Channel中读取数据。
NioEndpoint处理三件事:
- 接收连接;
- 处理IO事件;
- 处理请求;
//todo tomcat非阻塞IO、异步IO和APR
高并发就是能快速处理大量请求,要合理设计线程模型让cpu忙起来,尽量不让线程阻塞,一旦阻塞,cpu就闲下来。