IO非JAVA独有的,想要搞明白这些原理要从根来说起,根其实就是操作系统
UNIX操作系统下面,I/O 分为5种,同步阻塞I/O;同步非阻塞IO;I/O多路复用;信号驱动I/O;异步I/O
这些名字经常听到,常常是感觉自己懂了,但是真正聊起来的时候,又不能几句话清除明了的表达出来。带着以下问题来梳理上面的名字
1.这些I/O模型有什么区别?
2.同步和阻塞似乎是同一回事,到底有啥不同?
3.什么是I/O
4.为啥需要I/O
I/O 定义
所谓I/O就是计算机内存与外部设备拷贝数据的过程,我们知道CPU访问内存的速度远远高于外部设备,因此CPU先把外部设备的数据拷贝到内存中,然后再进行处理。
请考虑一下如下场景:当你的设备通过CPU向外部设备发出一个读指令的时候,数据从外部设备拷贝到内存需要一段时间,这个时候CPU没事干了,你的程序主动把CPU让给别人?还是让CPU不停的查,数据到了吗?数据到了吗?
这就是I/O模型要解决的问题。
下面重点分析I/O模型的区别
JAVA 的I/O 模型
对于一个网络I/O通信过程,比如网络数据的读取,会涉及两个对象。一个是调用I/O操作的用户线程,另外一个就是操作系统的内核了。一个进程的地址空间分为 用户空间,内核空间。用户线程不能直接访问内核空间。
当用户发起I/O操作后,网络数据读取会经历两个步骤。
1.用户线程等待内核将数据从网卡拷贝到内核空间。
2.内核将数据从内核空间拷贝到用户空间。
各种I/O模型的区别就是他们实现这两个步骤的方式不一样。
同步阻塞
用户线程发起read调用后就阻塞了,让出CPU。内核将数据从网卡拷贝到内核,然后从内核拷贝到用户空间。再把用户线程叫醒。
同步非阻塞
用户线程不断发起read调用,数据没有到内核空间时,每次都会返回失败,直到数据到达内核空间。这一次调用后,在等待数据从内核空间拷贝到用户空间这段时间里,线程还是阻塞的。等到数据到达用户空间,把线程叫醒。
I/O多路复用
用户线程的读取分为两个步骤:
线程发起select调用,目的是问内核数据准备好了吗?等数据准备好了,用户线程再发起read调用。在等待数据从内核空间拷贝到用户空间的时间里,线程还是阻塞的。为啥叫I/O多路复用呢?因为一个select 可以向内核查询多个通道(channel)的状态。所以叫多路复用。
异步I/O
用户线程发起read调用的时候注册一个回调函数,read立即返回。当内核将数据准备好之后,再调用指定的回调函数,进行处理。在这个过程中,用户线程一直没有阻塞。
高并发的思路
高并发就是能够快速处理大量的请求,需要设计合理的线程模型让CPU忙起来,尽量不要让线程阻塞,因为一阻塞CPU就闲下来了。另外就是有多少任务,就需要有多少相应规模的线程数去处理。I/O 请求核心处理三件事情:
1.接收链接
2.检测I/O事件
3.处理请求
最核心的就是把这三件事给分开处理
专门的线程组来接收acceptor
专门的线程组来 检测I/O事件
具体的任务执行也有专门的线程组
重点分析
I/O模型是为了解决内存与外部设备速度差异问题。
阻塞与非阻塞
我们平时说的阻塞非阻塞是指应用程序发起I/O操作的时候是立即返回,还是等待。
同步与异步
应用程序与内核通信的时候,数据从内核空间到应用空间的拷贝,是由内核主动发起的,还是由应用程序触发的。