Java IO 模型

Java的IO模型分为BIO,NIO和AIO,本文重点介绍BIO,NIO。只从原理角度介绍,比较他们的异同和各自特点,分析linux提供的IO系统调用及Java NIO实现原理。并且介绍一下NIO具体实现netty框架。

1.Linux IO模型和系统调用

    Linux系统结构如下所示:

架构

    Linux的内核定义了TCP/IP协议簇的实现,系统调用提供了API供应用系统调用。Java Linux版本虚拟机就是调用Linux系统调用实现的,在说明Java io模型之前有必要先了解一下Linux系统调用提供的IO模型、事件模型和并发模型(线程模型)。

    在Linux系统中将文件和网络连接等资源都定义为文件描述符,简称fd,对于网络连接成为socket,socket只是一种网络编程接口,可以理解为是TCP协议/UDP协议的实现,使用socket接口可以编写出应用层程序,例如应用服务器和FTP服务器等。

1.1 IO模型

    IO模型分为阻塞IO,非阻塞IO,IO复用和信号触发机制四种。

    阻塞IO执行的系统调用,例如socket的接收请求,建立连接,数据读取,数据写入等操作都会被系统挂起,直到等待事件到来为止;

    非阻塞IO执行的系统调用后立刻返回,不需要等待事件完成。非阻塞IO通常会与IO复用模型共同使用才会起编写出高效的程序;

    IO复用是程序通过系统调用IO复用函数向内核注册一组事件,内核当有事件到来时,触发程序执行,通常IO复用程序会同时监听多路socket来提高效率。Linux常用的IO复用函数有select,pool,epool。

    信号触发机制类似于IO复用,注册FD到目标宿主进程,宿主进程捕捉到FD的就绪时间,然后触发信号处理程序操作FD进行非阻塞操作。

    阻塞IO,IO复用,信号触发机制是同步IO操作,表现在触发事件时就绪事件,需要程序自己执行用户空间和内核空间的数据。非阻塞IO是异步操作,表现在所触发事件都是IO完成时间,即不需要程序自己拷贝用户空间和内核空间数据,由系统自动完成。

1.2 事件处理机制

    在IO复用模型中会产生大量的IO事件,本节介绍两种高效的事件处理模型。

    同步IO模型通常使用Reactor模型,异步IO模型通常使用Proactor模型。

1.2.1 Reactor模型

    主线程只负责监听FD上的是否有事件发生,当有事件发生时,交给工作线程执行。工作线程负责建立连接,读写数据等操作。

1.2.2 Proactor模型

    主线程处理IO操作,工作线程处理业务逻辑。

1.2.3模拟Proactor模型

    主线程负责监听IO事件和读操作,工作线程负责处理业务逻辑。

1.3并发模型

1.3.1半同步/半异步并发模型

    同步用于处理客户逻辑,并发用于处理IO请求。它有很多种变体。

1.3.2领导者/追谁者模型

    一个线程池中有只有一个线程(领导者)处理IO接受事件,当接收到事件时自己处理其业务逻辑,并且选举出一个新的领导者,到该线程处理完成时已经有领导者则其变为追谁者,如果还没有产生领导者则自己成为领导者线程,这种模型好处是不需要进行线程上下文切换,也不许访问共享数据结构,不会对资源加锁,执行效率较高。

2.java nio介绍和优势

    nio基于IO多路复用,事件驱动机制提高了IO处理能力,尤其提高了在长连接,大并发情况的性能。

    IO操作可以分为建立连接,监听,接受请求,读取,写回,关闭等步骤。BIO操作使用一个线程来处理一个连接的所有步骤,当没有请求到来,连接不可读、不可写的时候会阻塞线程(可以理解为一个线程在不断间隔一段时间询问一下是否可用),这样会导致CPU线程的频繁切换,并且绝大多数是无用的切换,不适应于存在大量闲置状态的长连接,如果是短连接效果比较好。

    而NIO在这个方面做了改进,若干监听器监听所有的SOCKET,当有事件到来时候在触发线程执行读操作,业务逻辑操作,写操作。监听器(selector)同时监听多个channel,甚至一个监听器可以监听所有channel的事件,selector的select方法在没有事件时候阻塞,有事件时候返回,调用selector.selectedKeySet()获取事件(连接每个阶段定义为事件,分为接受请求,可读,可写,连接上),交与业务线程去执行。只有若干的selector处于阻塞状态,其他业务线程如果没有业务处理则处于休眠状态,不耗费CPU时间。相比BIO每个连接都有一个线程阻塞确实节省了线程资源和CPU上下文切换时间。这在连接数很大,大部分连接处于空闲的情况下,性能提升尤为明显。

    nio使用预先分配的buffer进行预读数据和预写数据,提高了发送和接收效率。当写入数据时候,先将数据写到buffer,当channel可写时候,写入到channel;当数据可读的时候先将数据读入到buffer,然后触发业务处理线程去处理buffer数据。

3.netty

    netty是基于java nio实现的高效网络处理框架。netty的boss线程池负责监听端口上socket连接和断开事件,当有连接到来时创建Channel对象交给Worker线程池处理,Worker线程池负责监听channel上的可读可写事件,当有事件到来时创建ChannelPipeline(Channel事件的处理单元,一个Channel对应一个ChannelPipeline)来处理,ChannelPipeline上定义了ChannelHandler(读写操作处理单元,可有多个)用来处理读写事件,在ChannelHandler上通常会使用业务线程池来处理耗时的请求。

    ServerBootstrap负责初始化server,包括绑定端口、设置Channel工厂、Boss线程池、Worker线程池、设置ChannelPipeline创建工厂、ChannelPipeline注册若干ChannelHandler,ChannelPipeline注册业务处理线程池等操作。

    Boss线程池中每个线程监听一个端口,当有Socket到来时,Boss线程调用ChannelFactory创建Channel处理该Socket,然后请求Worker线程池的worker线程处理该Channel,一个Worker线程有一个selector监听多路Channel的读写事件,即IO复用,worker线程池默认数量为机器核心数的2倍。Worker线程循环读取selector,当有事件到来时调用ChannelPipelineFactory创建channelPipeline(一个Channel对应一个ChannelPipe,ChannelPipe上绑定了多个ChannelHandler)执行,ChannelHandler分为ChannelUpstreamHandler和ChannelDownstreamHandler,分别处理读取和写入事件。ChannelHandler是在worker线程中串行执行的,如果业务逻辑处理比较耗时,则会增加worker线程时间,处理业务逻辑建议使用业务处理线程池来处理。写入操作比较特殊,业务逻辑线程接受到入内容,然后写入到buffer,当worker线程接收到可写事件后,将buffer中消息内容写到channel中。

猜你喜欢

转载自gcc2ge.iteye.com/blog/2178987
今日推荐