高性能网络编程必备技能之IO与NIO阻塞分析

1.阻塞与非阻塞是什么?

阻塞:做某件事情,直到完成,除非超时,如果没有完成,继续等待;

非阻塞:做一件事情,尝试着做,如果说不能做完,就不做了,意思就是直接返回,如果能够做完,就做。

NIO的特性/NIO与IO区别:

1)IO是面向流的,NIO是面向缓冲区的;

2)IO流是阻塞的,NIO流是不阻塞的;

3)NIO有选择器,而IO没有。

传统IO模型

Java NIO架构

其实Java NIO模型相对来说也还是比较简单的,它的核心主要有三个,分别是:Selector、Channel和Buffer,我们先来看看它们之间的关系:

它们之间的关系很清晰,一个线程对应着一个Selector,一个Selector对应着多个Channel,一个Channel对应着一个Buffer,当然这只是通常的做法,一个Channel也可以对应多个Selector,一个Channel对应着多个Buffer。

Selector

个人认为Selector是Java NIO的最大特点,之前我们说过,传统的Java IO在面对大量IO请求的时候有心无力,因为每个维护每一个IO请求都需要一个线程,这带来的问题就是,系统资源被极度消耗,吞吐量直线下降,引起系统相关问题,那么Java NIO是如何解决这个问题的呢?答案就是Selector,简单来说它对应着多路IO复用中的监管角色,它负责统一管理IO请求,监听相应的IO事件,并通知对应的线程进行处理,这种模式下就无需为每个IO请求单独分配一个线程,另外也减少线程大量阻塞,资源利用率下降的情况,所以说Selector是Java NIO的精髓,在Java中我们可以这么写:

// 打开服务器套接字通道

ServerSocketChannel ssc = ServerSocketChannel.open();

// 服务器配置为非阻塞

ssc.configureBlocking(false);

// 进行服务的绑定

ssc.bind(new InetSocketAddress("localhost", 8001));

// 通过open()方法找到Selector

Selector selector = Selector.open();

// 注册到selector,等待连接

ssc.register(selector, SelectionKey.OP_ACCEPT);

...

Channel

Channel本意是通道的意思,简单来说,它在Java NIO中表现的就是一个数据通道,但是这个通道有一个特点,那就是它是双向的,也就是说,我们可以从通道里接收数据,也可以向通道里写数据,不用像Java BIO那样,读数据和写数据需要不同的数据通道,比如最常见的Inputstream和Outputstream,但是它们都是单向的,Channel作为一种全新的设计,它帮助系统以相对小的代价来保持IO请求数据传输的处理,但是它并不真正存放数据,它总是结合着缓存区(Buffer)一起使用,另外Channel主要有以下四种:

FileChannel:读写文件时使用的通道

DatagramChannel:传输UDP连接数据时的通道,与Java IO中的DatagramSocket对应

SocketChannel:传输TCP连接数据时的通道,与Java IO中的Socket对应

ServerSocketChannel: 监听套接词连接时的通道,与Java IO中的ServerSocket对应

当然其中最重要以及最常用的就是SocketChannel和ServerSocketChannel,也是Java NIO的精髓,ServerSocketChannel可以设置成非阻塞模式,然后结合Selector就可以实现多路复用IO,使用一个线程管理多个Socket连接,具体使用可以参数上面的代码。

Buffer

顾名思义,Buffer的含义是缓冲区,它在Java NIO中的主要作用就是作为数据的缓冲区域,Buffer对应着某一个Channel,从Channel中读取数据或者向Channel中写数据,Buffer与数组很类似,但是它提供了更多的特性,方便我们对Buffer中的数据进行操作,后面我也会主要分析它的三个属性capacity,position和limit,我们先来看一下Buffer分配时的类别(这里不是指Buffer的具体数据类型)即Direct Buffer和Heap Buffer,那么为什么要有这两种类别的Buffer呢?我们先来看看它们的特性:

Direct Buffer:

直接分配在系统内存中;

不需要花费将数据库从内存拷贝到Java内存中的成本;

虽然Direct Buffer是直接分配中系统内存中的,但当它被重复利用时,只有真正需要数据的那一页数据会被装载到真是的内存中,其它的还存在在虚拟内存中,不会造成实际内存的资源浪费;

可以结合特定的机器码,一次可以有顺序的读取多字节单元;

因为直接分配在系统内存中,所以它不受Java GC管理,不会自动回收;

创建以及销毁的成本比较高;

Heap Buffer:

分配在Java Heap,受Java GC管理生命周期,不需要额外维护;

创建成本相对较低;

阻塞点:

1.Socket socket = serverSocket.accept();

2. int data = is.read(b);

内存映射:

这个功能主要是为了提高大文件的读写速度而设计的。内存映射文件(memory-mappedfile)能让你创建和修改那些大到无法读入内存的文件。有了内存映射文件,你就可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问了。将文件的一段区域映射到内存中,比传统的文件处理速度要快很多。内存映射文件它虽然最终也是要从磁盘读取数据,但是它并不需要将数据读取到OS内核缓冲区,而是直接将进程的用户私有地址空间中的一部分区域与文件对象建立起映射关系,就好像直接从内存中读、写文件一样,速度当然快了。

关注我:加QQ群938837867回复“555”获取往期Java高级架构资料、源码、笔记、视频

Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术

  • 发表于:
  • 原文链接:https://kuaibao.qq.com/s/20181023A12VX300?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券