资讯专栏INFORMATION COLUMN

Java IO初探

edgardeng / 2529人阅读

Java IO对大多数Java程序员来说是熟悉又陌生,熟悉的是感觉到处都有它的身影,小到简单的读取文件,大到各种服务器的应用,陌生的是Java IO背后到底是一个怎样的机制,今天就让我们去了解一下这位老朋友吧。本文不讲解Java IO如何具体使用,有这方面需求的同学可以自己查下。

IO模型

要说IO,就不得不说IO模型,IO模型大家都有所了解,同步异步,阻塞非阻塞什么的,总的来说IO模型可分为以下五种:

阻塞IO

非阻塞IO

多路复用IO

信号驱动IO

异步IO

那么这几种IO都有什么区别呢?下面我们一一来看,每种模型我都会举一个适当的例子助于理解:

1.阻塞IO

阻塞IO相信大家都最熟悉了,线程发起一个IO请求,直到有结果返回,否则则一直阻塞等待,比如我们平常常见的阻塞数据库操作,网络IO等。

小明阻塞IO吃饭:

五年前一天周末,小明和朋友一起去商场的外婆家吃饭,到店后发现排队的人超多,所以他就领了一个号码,然后他和朋友就坐在旁边等候,一直等着服务员叫他们的号,也不能做其他事,过了一个多小时终于轮到他们了,然后他们进店点菜,又得等待上菜,最后他们吃饭总共花了两个小时;

关键部分:

等待座位吃饭:一直阻塞,直到有座位

等待上菜:一直阻塞,直到有菜(假设菜上齐了再吃)

没什么说的,反正就是一直等,反应到程序中就是一直阻塞,而一个IO请求需要一个线程,可想而知当有大量的IO请求,线程的创建和销毁,线程间的切换,线程所占用的资源等等要耗费多少时间和资源,系统的性能会有多差。

2.非阻塞IO

非阻塞IO和阻塞IO的最大区别就在于线程发起一个IO请求,不会一直堵塞直到有数据,而是不断的检查是否已有数据,若有数据则读取数据。

小明非阻塞IO吃饭:

有了第一次的教训,小明学乖了,他在拿到后不再傻傻的等着,而是去外婆家旁边逛了逛,每过3分钟他就会回来,然后跑到前台去询问服务员轮到他了吗?不幸的是,排队的人超多,直到过了半个多小时后才轮到他进店吃饭,期间他大概问了十几次,他们进店点菜,又得等待上菜,最后他们吃饭总共花了两个小时,基本也没做啥其他事;

关键部分:

领号后询问是否轮到他:非阻塞,非询问期间可以做点别的事,但也不做了啥大事

等待上菜:一直阻塞,直到有菜(假设菜上齐了再吃)

总的来说非阻塞IO的非阻塞主要体现在不需要一直等待到有数据,当然读数据那部分操作还是阻塞的,另外这种非阻塞模式需要用户线程自己不断询问检查,其实效率也不是太高,实际编程中运用的也不多。

3.多路复用IO

既然上面我们说到非阻塞IO的缺点,那么有没有什么方式改进呢?答案是当然有,那就是多路复用IO,我理解的它的特点就是复用,首先它也是一种非阻塞IO的模型,只不过上面说到轮询的方式用了不同的方式处理了,当一个线程发起IO请求,系统会将它注册到一个多带带管理IO请求的一个线程,之后该IO的相关操作的通知状态都有这个管理IO请求的线程处理,Java 1.4发布的NIO就是这种模式,我们可以大致来看一下它的流程:

// 打开服务器套接字通道
ServerSocketChannel ssc = ServerSocketChannel.open();
// 服务器配置为非阻塞
ssc.configureBlocking(false);
// 进行服务的绑定
ssc.bind(new InetSocketAddress("localhost", 8008));
// 这里的selector就相当于多带带管理IO请求的线程
Selector selector = Selector.open();
// 注册到selector,等待连接
ssc.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    selector.select();  //为IO请求去轮询状态
    Set keys = selector.selectedKeys(); //多个IO请求的状态
    Iterator keyIterator = keys.iterator();
    while (keyIterator.hasNext()) { //依次处理IO请求
        SelectionKey key = keyIterator.next();
        doThing(key)
        ...
    }
}

可以看出Java NIO的模式就是多路复用IO模型的应用。

小明多路复用IO吃饭:

随着生意越来越好,外婆家发现好多顾客都堵在门口等待吃饭,等待区都站不下来人了,,思来想去,外婆家准备请一个人专门来维护顾客的排队请求,这样顾客取号后,就不用堵在门口了,我们叫他小A,小明这次取号后,将自己的相关信息告诉小A,并从小A那里获得了一个GPS(用于小A能快速找到小明,假设有了GPS后,小A能秒速找到小明),然后小明就跟朋友们开心的去逛商场,看看MM,买买衣服,而小A则不断的观察店里的情况,当有空座位出现的时候,他便会按照相关信息找到具体的顾客,将其带回进行用餐,但他们进店点菜,还得等待上菜,最后他们吃饭总共花了两个小时,但是他们不再需要排队等位,而是去做一些其他的事。

关键部分:

领号后委托给小A,小A观察到有空位后带回小明:非阻塞,领号后可以安心去做自己的事,不用担心错过

等待上菜:一直阻塞,直到有菜(假设菜上齐了再吃)

多路复用IO可以看成普通非阻塞IO的升级版,也是目前Java编程中用到比较多的IO模型,它的优势在于可以处理大量的IO请求,用一个线程管理所有的IO请求,无需像阻塞IO和非阻塞IO一样,每个IO需要一个线程处理,提升了系统的吞吐量。

4.信号驱动IO

信号驱动IO相对于以上几种模型最大的特点就是它支持内核信号通知,线程在发起一个IO请求后,会注册一个信号函数,然后内核在确认数据可读了,便会给相应的线程发送通知,让其进行具体IO读写操作。

小明信号驱动IO吃饭:

又了一段时间,外婆家通过使用复用IO模式缓解了排队拥挤的情况,但是觉得还要请一个人专门维护队列,感觉不划算,那么有没有一种更好的方式呢?经过一天的苦思冥想,外婆家的经理又想出一个好办法,让每个顾客在领完号后,关注一下外婆家的公众号,然后顾客就可以去做别的事了,定时或者当排队信息发生改变时给顾客发送通知,告知他现在的排队序号或者轮到他吃饭了,顾客可以根据相应的信息做相应的行为,比如快轮到了就开始往店里走(实际程序中并不一定有这种状态,这里只是大概模拟),或者轮到自己了然后进店吃饭,他们仍然不用排队等位,而是去做一些其他的事。

关键部分:

领号后关注公众号,注册关系:非阻塞,领号后可以安心去做自己的事,不用担心错过

等待上菜:一直阻塞,直到有菜(假设菜上齐了再吃)

就实际来说,信号驱动IO用的并不多,因为信号驱动IO底层是使用SIGIO信号,所以它主要使用在UDP协议上,因为UDP产生SIGIO信号的时候只有两种可能:

1.要么数据到达

2.发生错误

但相对TCP来说,产生SIGIO信号的地方太多了,比如请求连接,确认,断开,错误等等,所以我们很难根据SIGIO信号判断到底发生了什么。

5.异步IO

以上四种IO其实都还是同步IO,因为它们在读写数据时都是阻塞的,异步IO相较于它们最大的特点是它读写数据的时候也是非阻塞的,用户线程在发起一个IO请求的时候,除了给内核线程传递具体的IO请求外,还会给其传递数据缓冲区,回调函数通知等内容,然后用户线程就继续执行,等到内核线程发起相应通知的时候,说明数据已经准备就绪,用户线程直接使用即可,无需再阻塞从内核拷贝数据到用户线程。

小明异步IO吃饭:

有过了一段时间,小明又想吃外婆家了,但是这个周末他并不想出门,他突然在网上看到新闻说外婆家竟然可以叫外卖,小明高兴坏了,他马上打电话给外婆家,告诉它自己想要吃哪些菜(相当于IO请求所需要的数据),然后将自己的联系号码(相当于回调通知)和住址(相当于数据缓冲区)也告诉它,然后就挂掉电话,开心的做去打游戏了,过了半个小时后,手机响起,告知外卖已经到了,小明开门取外卖就可以直接开吃了。整个过程小明直到吃饭都没有等待阻塞。

关键部分:

叫外卖并提供相应的信息:非阻塞,打完电话后做自己的事

通知外卖到了:直接开门取外卖直接开吃,非阻塞

我们可以看出,异步IO才是真正的异步,因为它连数据拷贝这个过程都是非阻塞的,用户线程根本不用关心数据的读写等操作,只需等待内核线程通知后,直接处理数据即可,当然异步IO需要系统内核支持,比如Linux中的AIO和Windows中的IOCP,但是也可以通过多线程跟阻塞I/O模拟异步IO,比如可以在多路复用IO模型上进行相应的改变,另外也有现有的实现,比如异步I/O的库:libeio

最后用一张图总体概括一下Java IO(图片来自美团技术博客):

Java IO概图:

多路复用IO在Linux中的实现

因为后续会讲到Java NIO,所以我们需要了解操作系统是如何支持多路复用IO的,Linux中支持支持三种多路IO复用机制,分别是select、poll和epoll,本来这里我想自己写的,但查阅了相应的一些资料后,发现自己的水平还是不够,这里我不准备班门弄斧了,因为我找到了很多写的比较好的文章,这里就给大家列一下,仅供参考:

Linux系统编程——I/O多路复用select、poll、epoll的区别使用

聊聊IO多路复用之select、poll、epoll详解

IO 多路复用是什么意思?

总结

这篇文章主要讲了最基础的IO模型,不过我认为最基础的往往是最重要的,只有理解了基础的原理,才能对基于它们实现的类库或者工具有更加深刻的认识,下一篇文章将会主要讲一下基于多路复用IO的Java NIO,敬请期待。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67882.html

相关文章

  • 初探Java Socket

    摘要:绑定完成后允许套接字进行连接并等待连接。服务端根据报文返回响应,并关闭连接。单线程服务器多进程及多线程服务器复用服务器复用的多线程服务器单线程服务器一次只处理一个请求,直到其完成为止。 前言 本篇文章将涉及以下内容: IO实现Java Socket通信 NIO实现Java Socket通信 阅读本文之前最好了解过: Java IO Java NIO Java Concurrenc...

    张汉庆 评论0 收藏0
  • Motan+Zipkin+Brave链路跟踪初探

    0.Why Zipkin 随着业务发展,系统拆分导致系统调用链路愈发复杂一个前端请求可能最终需要调用很多次后端服务才能完成,当整个请求变慢或不可用时,我们是无法得知该请求是由某个或某些后端服务引起的,这时就需要解决如何快读定位服务故障点,以对症下药。于是就有了分布式系统调用跟踪的诞生。而zipkin就是开源分布式系统调用跟踪的佼佼者 zipkin基于google-Dapper的论文有兴趣的可以看下...

    woshicixide 评论0 收藏0
  • 慕课网_《Hibernate初探之多对多映射》学习总结

    时间:2017年07月11日星期二说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com教学源码:无学习源码:https://github.com/zccodere/s... 第一章:应用场景 1-1 多对多的应用场景 案例分析:企业项目开发过程中 一个项目可由多个员工参与开发 一个员工可同时参与开发多个项目 示意图 showImg(https://segmentfau...

    caozhijian 评论0 收藏0
  • Netty4.x 源码实战系列(一):ServerBootstrap 与 Bootstrap 初探

    摘要:而用于主线程池的属性都定义在中本篇只是简单介绍了一下引导类的配置属性,下一篇我将详细介绍服务端引导类的过程分析。 从Java1.4开始, Java引入了non-blocking IO,简称NIO。NIO与传统socket最大的不同就是引入了Channel和多路复用selector的概念。传统的socket是基于stream的,它是单向的,有InputStream表示read和Outpu...

    BakerJ 评论0 收藏0
  • 初探Vector

    摘要:现在用的比较少了只作为了解一类的声明主要看的是实现了接口二构造方法构造一个空,初始大小为,其标准容量增量为零。构造一个具有指定初始容量的空,其容量增量为零。 ps:现在Vector用的比较少了,只作为了解 一、类的声明 主要看的是实现了List接口 public class Vector extends AbstractList implements List, Ran...

    suemi 评论0 收藏0

发表评论

0条评论

edgardeng

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<