NIO基础-多线程-线程池-selector的区别

NIO基础

在这里插入图片描述
在这里插入图片描述

单向通道:

输入输出流是数据传输的通道,只不过是单向通道。比如说input stream,它只能从文件、从网络去读入数据。那output stream只能向文件、网络里去输出数据,是单向的,

双向通道

channel是双向的数据通道。既可以用来输入数据,也可以用来输出数据。
输入数据得把这个数据要临时存储在内存当中,buffer就是一个缓冲区,它是一个内存缓冲区,用来暂存从channel中读入的数据。或者反过来,如果你想向那里写数据的时候,也得先把数据暂存在buffer里。

  • FileVhannel:作为文件的数据传输通道。

  • DatagramChannel:做udp网络编程时,是一个数据传输通道

  • socketChannel以及server socket channel视作TCP实时数据的传输通道

  • buffer:以字节为单位来缓冲数据的,它是一个抽象类,buffer是一个抽象类的实现类,有map的buffer 等,他就是支持的不同数据类型的缓冲区啊,比如说有short buffer,Float double char,其实就对应着不同的基本数据类型。不过后面这些用的都较少,主要最为常用是byte

多线程

在这里插入图片描述
客户端进行读写操作,在服务器端可以启动一个新的线程来为socket提供服务,进行读写操作都是在这个线程内完成的。
那如果有多个客户端,有多个socket连接,那服务器就需要开多个线程。每个线程专管一个链接。这就是多线程版的服务器端的设计。
这种设计有什么弊端或缺点呢?高连接数会瘫痪
我们的连接数比较少的时候,这种多线程版的设计是没有问题的。但是要设计一个处理大批量的连接的服务,它就不行。因为。一个客户端用一个线程去处理的话,这个线程本身它就会占用一定的内存。默认的情况下,就比如说Windows下默认的一个线程会占用一兆的内存。你来1000个。客户端建立了1000个连接,线程占用的内存数那就是1000,乘以一兆就是一个G啊。显然,随着连接数的增加,内存首先撑不住了,说不定就out of memory。内存溢出了。这里打一个比喻啊。就好比服务端就是一个餐馆,这些个socket就是来餐馆就餐的客人。那我们多线程版的这个服务器设计就好比是:一个客人对应一个服务员。
同时,线程数多了,CPU也跟得上才行。因为真正去执行线程内的代码。要靠CPU来执行。比如说CPU只有16,那其实能够真正同时执行的这个代码,或者说只能够同时去跑的线程数也只有16个。那其他更多的线程就得等待。那这些线程等待的时候。它就会把这些线程的当前的执行状态加以保存,比如说你线程执行到第几行代码了,线程中的一些临时变量是什么,他都得把它记录保存下来。多出来的线程就得一边儿歇着去。那将来轮到这些线程运行了,他又得把这些状态进行恢复。这就叫线程的一个上下文切换,这个成本是比较高的,
就好比餐馆的例子。我这个餐馆可能一共就16个工位。你能同时来的服务员也只能有16个,但剩余的服务员没有工位的,干不了活儿的,你只能在餐馆外面等着,轮到你了你才能进来,显然成本太高,你中间这个腾出工位,占用工位这个时间。那就把整个服务处理的时间就拖慢了。所以这是多线程版的第二个缺点。综上所述,这种多线程版的服务器端的设计,他只是和连接数较少的场景。连接数一高,它就不能很好的工作

线程池

在这里插入图片描述

线程池是否能解决前面的问题呢?
上边这个图,就表示有一个服务端采用了线程池的一个设计,线程池内线程数是有限的,比如说就两个线程,将来客户端可以有很多,比如说有四个客户端,那这个线程能不能处理多个客户端的这个读写操作呢?当然是可以的,但是有一定的限制,什么限制呢?就是我们这个socket是一个阻塞模式。
什么叫阻塞模式?简单的说就是阻塞模式下,这个线程,同一时间内只能处理一个socket的连接上的这个操作,读写操作。比如说我们这张图里啊,看我画了一个实线,画了虚线,也就是这个线程,他处理SOCKET1的同时。就不能处理socket3。他得等到socket1断开了以后才能腾出手来。去处理socket3上的连接,
这个打一个比喻啊,餐馆的例子。socket是我们的服务员。那这个socket1呢,客人就餐的话可能有很多的步骤,他要翻菜单,要点菜,要吃饭,要最后买单结账,对吧,但是在他执行所有操作的这个同时,这个服务员都必须跟着陪着。就比如说他在这儿光翻菜单不点菜,都在这个线程内。直到最后这个客人啊,他都结结账完了,他把这个连接断开了,这个服务员才得到自由,才能去处理下一个课程。这就叫阻塞模式。同一时间,一个线程只能处理一个socket
这种短连接,就是你送给他连上以后,你做完一项业务处理,赶紧把连接断开,断开连接的目的是什么呢?就是让这个线程能够腾出手来去处理其他的链接。就好比我们早期的他们开的服务器啊,它就是采用了这种。现场直板的设计啊,阻塞式的io比较适合我们的HTTP那种请求。因为HTTP请求就是连上去发一个请求返回响应了就可以断开了,这样我这个线程就解放出来,可以处理下一个请求。这是线程池板的一个弊端。
总结一下:只适合短连接的场景,而且这种线程池版的情况下,由于socket工作在阻塞模式下,导致线程的利用率不高

selector

在这里插入图片描述
打一个比喻啊,thread就好比我们餐馆的服务员,但是我们通过这张图注意到,这个服务员只需要有一个就够了。那这个channel,就好比那些客人,channel就等同于我们之前的socket。他其实也就是代表了一个服务器跟客户端之间的连接吗?数据可以通过这个channel在服务器跟客户端之间进行数据的读写啊,它是数据读写的通道。
selector就好比一个能够监测到所有客人需求的这么一个工具。你把它理解成。一个摄像头也好,或者什么也好,反正客人的一举一动都在他的监视之内。一旦这些客人有些什么请求,selector它就能够在第一时间知道,然后派这个服务员去提供服务。比如说啊,我们一开始这些客人啊,这些selector都连到服务上啊,刚开始大家都在翻菜单儿。没做任何的操作,后来有一个客人。这个客人吧。他决定好了,点菜了啊,点个宫保鸡丁,它就会把这个需求告诉我们的啊,我有一个有些数据要发送给服务器了,服务器给selector,那selector交给这个thread,然后这个thread去处理我们这位客人的请求。那如果是接下来又有一位客人
selector可以管理多个产能啊、管理多个层次、可以获得这些channel上所有发生的事件。但这个事件类型分为可连接可读可写这些事件。那一旦这些事件发生了,这个select呢,就可以让配合一个线程,让这个线程来具体处理这些读写的操作。并且还有一点,他跟我们前面讲的那个线程池版本最大一个区别就是channel,它是工作在一个非阻塞模式下。非阻塞就不会像以前我们一个socket连接没断开之前,这个线程就在那儿。非阻塞简单的理解就是它不会让唯一这个线程吊死在一个channel上。你第一个channel没有时间,那这个线程可以去处理第二channel,第二个channel没有时间,他线程可以处理第三个,我发现这样做,这个线程他始终有活儿干,他不会因为一个吊死在一个连接上,他就不能去服务其他的客人。是这样吧,这样的话,这个现成的利用率是不是得到了极大的提升。
总结一下,我们用select以后啊,首先线程的数目。一个人一个线程就够了,一个人可以去配合select去轮流处理多个channel上的读写事件,一个线程处理了多个channel,它的利用率得到了提高。那我们的SELECT1版适合什么?适合连接数特别多的产品。但是呢,流量比较低,什么叫流
量比较低啊,就是不是说这些客人频繁的去发这些读写操作啊。比如说一个客人。他如果是发送了大量的数据呢,我这个线程他可能就光处理这个人,这个客人发送了大量数据让他处理,期间其他的客人就遭到冷遇了,遭到冷遇啊,就是它适合这个数据量流量比较低的场景。selector,它其实起到了一个很核心的作用,他就是能监测到所有channel上的这些读写事件。读写事件发生的时候,他就会让我们的这个线程去跑去处理这些事情。这是select版针对之前我们服务器端设计的一些改进

猜你喜欢

转载自blog.csdn.net/sunrj_niu/article/details/129539355