java NIO深度讲解

1.NIO概述

由于java需要跑在JVM之上,JVM又为了java可以在不同的平台上运行,做了很多适配的工作,最明显的就是在IO领域。jvm采用的是流这种在各个平台通用的形式,虽然适用面广可以满足不同的平台,但是在操作大量数据的时候,由于不能发挥各个操作系统在IO上的特性,所以效率会又致命的缺陷。

在nio出现之后,对原来的bio做了一些列改进,由于IO可以分为网络的IO和磁盘的IO,所以我们这里也从这两个方面去介绍一下NIO的改进。

2.网络方面

这里先介绍一些传统的bio,再通过bio的劣势引出nio,这样更能体会到nio的改进。

================================================================================================

先提一下accept()方法:

每当一个请求到达服务端,都会缓存到一个队列中,这个队列中会保存客户端要做的操作,每当调用accept()的时候,都会从这个队列中取出一个请求来进行操作。 其他还在队列中等待的请求必须排队等待accept()去取它们。同时这些请求在等待的时候会一直记录客户端在等待的时间内发送过来的操作,这个请求被取出来之 后会将这段等待时间内客户端发起的所有动作做处理。处理完之后返回给客户端。

================================================================================================

(1)在jdk1.4及之前,当一个客户端使用socket请求服务端的时候,服务端通过accept()方法获得客户端的Socket连接后,由于服务器端需要做io操作,所 以服务端的线程会一致阻塞直到IO结束,在阻塞的这段时间内就不可以接收客户端的请求了,这就造成了客户端访问慢,并发量低的情况,比如:

 

每当accept()方法获得客户端的Socket连接后都会执行红圈内的代码,如果这段代码阻塞了,那就不会再执行socket.accept()(这个方法会从请求等待队列中取 出一个请求),也就无法处理 客户端请求,在用户看来就是服务器挂掉了。

(2)为此,jdk1.4及以前的解决方式是,另起线程或者使用线程池来执行IO操作,类似于:

 

新开线程的方式虽然解决了客户端连接的问题,但是却加大了CUP的压力,并发量高的时候上下文切换带来的开销也是很大的。并且访问量高的时候,会新开上 万条线程,对GC的要求会很高

(3)为了解决线程过多的问题,我们引入了线程池,让线程可以复用。

 

但是它只解决了线程过多的问题,但还是会有问题:

首先虽然复用了线程,但是线程频繁的上下文切换会带来不小的开销

其次有的时候,服务端要做的操作并不需要类似等待用户输入的IO操作,这样程序是不会阻塞的,反而新开线程会浪费很多资源。

最后,线程中的IO操作有时会一直阻塞,但是这条线程也还是会不停的抢夺CPU时间片。抢到了也不会做什么事,白白等待完时间片。所以对每个线程的资源利 用率不高

 

为了解决上面的问题真正的NIO来了!!!

Nio为了解决上面的问题,采用了selector和channel来构建了一个多路复用的模型,其中selector是单线程来运行的。我们首先将channel注册到selector之 上并且设置设个channel感兴趣的事件,之后selector会不断的去轮询网卡中的事件,一旦检测到channel感兴趣的事件就会记录保存在selector中的一个列表 里。用户在调用的时候直接通过selector中的事件可以找到事件对应的channel,拿到channel之后就可以得到内容。

是不是懵逼了?什么是事件?注册是什么意思?轮询网卡是什么意思?那是因为基础知识不到位,我们先来补一下基础。

什么是网络IO

计算机想要连接网络必须要有网线(或者wifi)和网卡,将网线接到网卡上,并且安装网卡驱动。这个驱动就是将网卡(硬件)和计算机操作系统(软件)联 系起来的一个软件。有了这些条件,每当一个来自网络的请求要发网我们的计算机,都会顺着网线发送到网卡。网卡将我们收到的请求解析(也就是网络的分 层模型),解析完之后就可以供操作系统和我们的应用使用了。我们所说的网络IO,其实也就是将网卡这个硬件中的内容读取到我们的内存中。

操作系统网络IO的过程

当我们在java中请求一个网络资源的时候,比如我们想要接收所有发送到我们计算机的请求做处理。当我们问网卡要这个资源的时候,这个资源有可能并没有 被网卡准备好,因为网卡需要去一层层的解析请求,解析完之后这个请求还要经过内核态和用户态的复制。在这段时间内我们的应用是无法获得这些内容的, 只有等待这个过程结束我们才能获得我们想要的东西,这也就是我们说IO比内存操作要慢的一个重要因素。

Java如何完成网络IO

首先我们应该明白,我们是如何通过代码去操作我们的计算机的。比如我们想去将字符串写入文件中,首先要在jvm中生成字符串,然后再通过jvm去调用 操作系统的write方法,将字符串写入文件。也就是说,我们只要想操纵我们的电脑,都需要通过jvm去调用操作系统提供的api去完成,io也不例外。之前的bio,我们在请求一个文件时,我们调用操作系统的read方法。如果操作系统还没有准备好这个文件,那么read方法是阻塞住的。但是在nio中,我们请 求一个文件,如果这个文件还没准备好,操作系统请求的方法会立刻返回,告诉我们还没好。这样我们就可以不断的去问操作系统好了没好没。如果好了再开 始处理。这其实靠操作系统epoll方法的支持,这个方法在资源没有准备好的时候会立刻返回。而不是像read那样阻塞。

NIO的原理简述

我们首先新建一个channel,这个channel会携带我们感兴趣的事件,比如要读取资源已经准备好,要写入的资源已经准备好。然后将channel注册到selector 上。这时候selector就会知道我们这个通道要处理的是哪些事件,selector去不停的询问网卡这个事件好了吗。一旦这个事件好了就会在selector内部将这个 已经好了的资源记录下来。等待我们的应用通过channel去取,这时channel取的时候就不会出现阻塞。

NIO更高效?

有很多人认为NIO比传统的BIO更加的高效,但其实不是。如果你仅仅使用的是一个Connection,BIO反而比NIO更加高效。Nio适用于连接数比较大且每个连接处理过程比较短的场景。所以NIO和BIO并没有绝对的好坏,要看使用场景决定。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值