阻塞式IO指从硬盘中读取数据的时候,一直等待程序读取完毕之后,再继续其他操作;而非阻塞式NIO指从硬盘读取数据的时候,程序可以继续向下执行,等数据读取完毕后,通知当前程序,然后此程序可以处理该数据或执行完当前操作之后再读取数据。NIO设计背后的基石:反应器模式(用于事件多路分离和分派的体系结构模式 ),用于事件多路分离和分派的体系结构模式。
在java源码中阻塞式IO,需要为每个连接创建一个线程,当并发的连接数量非常庞大的时候,线程所占用的栈内存与CPU线程相互切换开销将会非常庞大,使用java源码中非阻塞式NIO,可以用一个含有有限数量线程的线程池,一个线程可以为任意数量的连接提供服务,由于线程数量小于连接数量,因此每个线程进行IO操作时就不能阻塞,反之阻塞的话部分连接时得不到处理,非阻塞式NIO提供了这种能力。
下面为大家通俗易懂的方式分析一下如何实现小量的线程同时为大量的连接提供服务?
假设到餐厅吃饭,每一桌客人由一个专门提供服务的服务员,一对一服务,很明显成本高,如果餐厅生意好,同时来了100桌客人,就需要100个服务员,这就是传统模式的一个连接对应一个线程的方式。
大家想如果此时你是老板,不可能招100个服务员吧。现在的老板一个比一个精明着呢,老板琢磨怎么才能用现有的10个服务员同时为100桌客人提供服务呢?老板观察发现,服务员在为客人提供服务的过程中并不是一直都在忙,客人点菜上菜之后,在客户用餐的这段时间服务员是闲着的,可以该服务员被这桌客人占用了不能再为其他人提供服务,也就是我们经常说的“工作不饱和”。老板想了一个办法,让一个服务员专门负责收集客人需求,如:客人进门接待,客人点餐,客人结账等,按顺序一一记录,然后每个服务员到柜台领取需求,当服务员完成之后再领取下一个需求,继续为客人服务当客户数量增多时可能需要等待,服务质量不如一对一服务,但是好处很明显,服务员没有闲着的,原本10个服务员只能为10桌客人提供服务,此时可以更多的客人提供服务。
非阻塞式NIO与阻塞式IO的区别:
1)增加一个角色,专门收集客人需求的服务员,指NIO对应的Selector;
2)传统阻塞式IO操作,read(),当没有数据可读时,线程一直阻塞被占用,直到数据到来,而java源码中非阻塞式NIO没有数据可读时,read()会立即返回0,线程不会阻塞。
3)java源码中非阻塞式NIO中,客户端创建一个连接后,首先将连接注册到Selector,相当于客人进入餐厅告诉服务员用餐,服务员告诉你桌号,而桌号就是非阻塞式NIO中的SelectionKey。
通常的,对一个文件描述符指定的文件或设备, 有两种工作方式: 阻塞 与非阻塞 。所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 直到有东西可读或者可写为止。而对于非阻塞状态, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待 。
数据的读取速度方面,具体如下:
CPU > 内存 > 硬盘(I-从硬盘到内存 O-从内存到硬盘)
一种常用做法是:每建立一个Socket连接时,同时创建一个新线程对该Socket进行单独通信(采用阻塞的方式通信)。这种方式具有很高的响应速度,并且控制起来也很简单,在连接数较少的时候非常有效,但是如果对每一个连接都产生一个线程的无疑是对系统资源的一种浪费,如果连接数较多将会出现资源不足的情况。
另一种较高效的做法是:服务器端保存一个Socket连接列表,然后对这个列表进行轮询,如果发现某个Socket端口上有数据可读时(读就绪),则调用该socket连接的相应读操作;如果发现某个 Socket端口上有数据可写时(写就绪),则调用该socket连接的相应写操作;如果某个端口的Socket连接已经中断,则调用相应的析构方法关闭该端口。这样能充分利用服务器资源,效率得到了很大提高。