原创

NIO-Selector


selector单从字面上不好理解,需要结合服务器的设计演化来理解它的用途 下面就列举服务器的设计演化来说明

多线程版的设计

https://edu-1443.oss-cn-shenzhen.aliyuncs.com/L3%24Y%7E44OZZHGCT%7E%29%5DL7DT%28E.png

多线程版缺点
  • 内存占用高
  • 线程上下文切换成本高
  • 只适合连接数少的场景

服务器的应用程序开发要处理多个客户端的通信,一个客户端来了,在我们的代码里面表现是一个socket,我们针对这个socket就可以进行一些读写的操作,对这些读写操作,我们服务器端就可以启动一个线程来专门为这个socket提供服务,如果有多个客户端,那服务器端就开多个线程,每个线程专管一个连接

内存占用高

一个客户端用一个线程去处理的话,线程本身就会占用一定内存

这里打一个比喻,服务器就是一个餐馆,这些socket就是来就餐的客人,线程就是餐馆雇用的服务员,那多线程版的设计就好比是,一千个客人,就得雇用一千个服务员

线程上下文切换成本高

很多人有个误解,线程数越多,处理效率越高,其实这是有前提条件的,线程数多了,你cpu也得跟得上,真正执行代码还得靠cpu,比如我的cpu只有16核,那其实真正同时跑的线程数也只有16个,那其他更多的线程只能等待,等待的线程将来唤醒的,就叫做上下文切换,这个成本其实是比较高的

综上所述,这种多线程设计的服务器,只适合于连接数少的场景

线程池版设计

前面分析了多线程版的设计,主要的矛盾就是线程数太多了,导致内存占用高,线程的上下文成本高,那怎么解决呢,我要限制线程的数量,那我可以采用线程池设计呀

那线程池的设计又会有什么缺点呢

线程池版缺点
  • 阻塞模式下,线程仅能处理一个socket连接
  • 仅适合短连接场景

阻塞模式下只能处理一个连接

以上图为例thread必须等socket1断开之后,它才能去处理socket3的连接

以餐馆为例,thread是服务员,socket1这个顾客就餐它有很多步骤,有点餐,有就餐结账等等,但是在阻塞模式下,thread必须等socket1这个顾客把这些操作全部执行完了,它才能去服务socket3

仅适合短连接场景 想想看,如果有一个socket,连上后什么都不做,又是一个长链接,那线程岂不是陪着它耗了 所以这种就更适合http这种请求,因为http就是连上去发一个请求返回响应了就断开

selector版设计

有了上述铺垫,再来看selector版的设计

还是餐馆比喻,thread是服务员,但是服务员只需要一个,channel就好比客人,地位就等同于之前的socket,其实也是代表一个服务器到客户端的连接,中间是selector,它是一个能够监测到所有客人需求的这样的工具,你把他理解为摄像头也可以,反正就是客人的一举一动都在它的监视内,一旦客人有什么请求,selector第一时间就会知道,然后让thread提供服务

selector的作用就是配合一个线程来管理多个channel,获取这些channel上发生的事件,这些channel工作在非阻塞模式下,不会让线程吊死在一个channel上。适合连接数特别多的场景

Java
  • 作者:刘柄岐
  • 发表时间: 2023-01-13 12:12
  • 版权声明:自由转载-非商用