Java IO模型

# Java IO模型

在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念:同步与异步,阻塞与非阻塞。关于同步和异步的概念解读困扰着很多程序员,大部分的解读都会带有自己的一点偏见。参考了 Stackoverflow相关问题后对原有答案进行了进一步完善:

When you execute something synchronously, you wait for it to finish before moving on to another task. When you execute something asynchronously, you can move on to another task before it finishes.

当你同步执行某项任务时,你需要等待其完成才能继续执行其他任务。当你异步执行某些操作时,你可以在完成另一个任务之前继续进行。

Java 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。

同步异步

  • 同步 :两个同步任务相互依赖,并且一个任务必须以依赖于另一任务的某种方式执行。 比如在A->B事件模型中,你需要先完成 A 才能执行B。 再换句话说,同步调用种被调用者未处理完请求之前,调用不返回,调用者会一直等待结果的返回。
  • 异步: 两个异步的任务完全独立的,一方的执行不需要等待另外一方的执行。再换句话说,异步调用种一调用就返回结果不需要等待结果返回,当结果返回的时候通过回调函数或者其他方式拿着结果再做相关事情

阻塞和非阻塞

  • 阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。
  • 非阻塞: 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。

如何区分 “同步/异步 ”和 “阻塞/非阻塞”

  • 同步/异步是从行为角度描述事物的,而阻塞和非阻塞描述的当前事物的状态(等待调用结果时的状态)。

# IO模型理解

根据UNIX网络编程对I/O模型的分类,在UNIX可以归纳成5种I/O模型

  • 阻塞I/O
  • 非阻塞I/O
  • I/O多路复用
  • 信号驱动I/O
  • 异步I/O

# 文件描述符

Linux 的内核将所有外部设备都看做一个文件来操作,对一个文件的读写操作会调用内核提供的系统命令(api),返回一个file descriptor(fd,文件描述符)。而对一个socket的读写也会有响应的描述符,称为socket fd(socket文件描述符),描述符就是一个数字,指向内核中的一个结构体(文件路径,数据区等一些属性)。

  • 所以说:在Linux下对文件的操作是利用文件描述符(file descriptor)来实现的

# 用户空间和内核空间

为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分

  • 一部分为内核空间
  • 一部分为用户空间

# I/O运行过程

我们来看看IO在系统中的运行是怎么样的(我们以read为例)

可以发现的是:当应用程序调用read方法时,是需要等待的--->从内核空间中找数据,再将内核空间的数据拷贝到用户空间的。

  • 这个等待是必要的过程

下面只讲解用得最多的3个I/0模型:

  • 阻塞I/O
  • 非阻塞I/O
  • I/O多路复用

# 阻塞I/O模型

在进程(用户)空间中调用recvfrom,其系统调用直到数据包到达且被复制到应用进程的缓冲区中或者发生错误时才返回,在此期间一直等待

# 非阻塞I/O模型

recvfrom从应用层到内核的时候,如果没有数据就直接返回一个EWOULDBLOCK错误,一般都对非阻塞I/O模型进行轮询检查这个状态,看内核是不是有数据到来。

# I/O复用模型

前面也已经说了:在Linux下对文件的操作是利用文件描述符(file descriptor)来实现的

在Linux下它是这样子实现I/O复用模型的:

  • 调用select/poll/epoll/pselect其中一个函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。

比如poll()函数是这样子的:int poll(struct pollfd *fds,nfds_t nfds, int timeout);

其中 pollfd 结构定义如下:

struct pollfd { 
	int fd; /* 文件描述符 */ 
	short events; /* 等待的事件 */ 
	short revents; /* 实际发生了的事件 */
};
1
2
3
4
5

  • (1)当用户进程调用了select,那么整个进程会被block;
  • (2)而同时,kernel会“监视”所有select负责的socket;
  • (3)当任何一个socket中的数据准备好了,select就会返回;
  • (4)这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程(空间)。
  • 所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符其中的任意一个进入读就绪状态,select()函数就可以返回

select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接

# I/O模型总结

正经的描述都在上面给出了,不知道大家理解了没有。下面我举几个例子总结一下这三种模型:

阻塞I/O:

  • Java3y跟女朋友去买喜茶,排了很久的队终于可以点饮料了。我要绿研,谢谢。可是喜茶不是点了单就能立即拿,于是我在喜茶门口等了一小时才拿到绿研。
  • 在门口干等一小时

非阻塞I/O:

  • Java3y跟女朋友去买一点点,排了很久的队终于可以点饮料了。我要波霸奶茶,谢谢。可是一点点不是点了单就能立即拿,同时服务员告诉我:你大概要等半小时哦。你们先去逛逛吧~于是Java3y跟女朋友去玩了几把斗地主,感觉时间差不多了。于是又去一点点问:请问到我了吗?我的单号是xxx。服务员告诉Java3y:还没到呢,现在的单号是XXX,你还要等一会,可以去附近耍耍。问了好几次后,终于拿到我的波霸奶茶了。
  • 去逛了下街、斗了下地主,时不时问问到我了没有

I/O复用模型:

  • Java3y跟女朋友去麦当劳吃汉堡包,现在就厉害了可以使用微信小程序点餐了。于是跟女朋友找了个地方坐下就用小程序点餐了。点餐了之后玩玩斗地主、聊聊天什么的。时不时听到广播在复述XXX请取餐,反正我的单号还没到,就继续玩呗。~~等听到广播的时候再取餐就是了。时间过得挺快的,此时传来:Java3y请过来取餐。于是我就能拿到我的麦辣鸡翅汉堡了。
  • 听广播取餐,广播不是为我一个人服务。广播喊到我了,我过去取就Ok了。
上次更新: 2020/07/25, 16:07:00
最近更新
01
RabbitMQ简介
10-27
02
聊聊Java多态
10-21
03
JVM垃圾回收器
10-16
更多文章>