首页 > 代码库 > Java I/O 工作机制(二) —— Java 的 I/O 的交互方式分析

Java I/O 工作机制(二) —— Java 的 I/O 的交互方式分析

简介:

BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。 
NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。 

BIO 
同步阻塞式IO,相信每一个学习过操作系统网络编程或者任何语言的网络编程的人都很熟悉,在while循环中服务端会调用accept方法等待接收客户端的连接请求,一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成。 
如果BIO要能够同时处理多个客户端请求,就必须使用多线程,即每次accept阻塞等待来自客户端请求,一旦受到连接请求就建立通信套接字同时开启一个新的线程来处理这个套接字的数据读写请求,然后立刻又继续accept等待其他客户端连接请求,即为每一个客户端连接请求都创建一个线程来单独处理,大概原理图就像这样: 
 

虽然此时服务器具备了高并发能力,即能够同时处理多个客户端请求了,但是却带来了一个问题,随着开启的线程数目增多,将会消耗过多的内存资源,导致服务器变慢甚至崩溃,NIO可以一定程度解决这个问题。

NIO 
同步非阻塞式IO,关键是采用了事件驱动的思想来实现了一个多路转换器。 
NIO与BIO最大的区别就是只需要开启一个线程就可以处理来自多个客户端的IO事件,这是怎么做到的呢? 
就是多路复用器,可以监听来自多个客户端的IO事件: 
A. 若服务端监听到客户端连接请求,便为其建立通信套接字(java中就是通道),然后返回继续监听,若同时有多个客户端连接请求到来也可以全部收到,依次为它们都建立通信套接字。 
B. 若服务端监听到来自已经创建了通信套接字的客户端发送来的数据,就会调用对应接口处理接收到的数据,若同时有多个客户端发来数据也可以依次进行处理。 
C. 监听多个客户端的连接请求和接收数据请求同时还能监听自己时候有数据要发送。 
 

总之就是在一个线程中就可以调用多路复用接口(java中是select)阻塞同时监听来自多个客户端的IO请求,一旦有收到IO请求就调用对应函数处理。 

一旦有请求到来(不管是几个同时到还是只有一个到),都会调用对应IO处理函数处理,所以:

(1)NIO适合处理连接数目特别多,但是连接比较短(轻操作)的场景,Jetty,Mina,ZooKeeper等都是基于java nio实现。

(2)BIO方式适用于连接数目比较小且固定的场景,这种方式对服务器资源要求比较高,并发局限于应用中。

 

同步与异步

同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)
同步:在发出一个调用时,在没有得到结果之前,该调用就不返回。一旦调用返回,就得到返回值了。调用者主动等待这个调用的结果。
异步:调用在发出之后就直接返回了,没有立刻得到返回结果。在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

在设计到 IO 处理时通常都会遇到一个是同步还是异步的处理方式的选择问题。因为同步与异步的 I/O 处理方式对调用者的影响很大,在数据库产品中都会遇到这个问题。因为 I/O 操作通常是一个非常耗时的操作,在一个任务序列中 I/O 通常都是性能瓶颈。但是同步与异步的处理方式对程序的可靠性影响非常大,同步能够保证程序的可靠性,而异步可以提升程序的性能,必须在可靠性和性能之间做个平衡,没有完美的解决办法。

 

阻塞与非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态

阻塞与非阻塞主要是从 CPU 的消耗上来说的,阻塞就是 CPU 停下来等待一个操作完成 CPU 才接着完成其它的事。非阻塞就是在这个操作在执行时 CPU 去干其它别的事,等这个操作完成时,CPU 再接着完成后续的操作。虽然表面上看非阻塞的方式可以明显的提高 CPU 的利用率,但是也带了另外一种后果就是系统的线程切换增加。增加的 CPU 使用时间能不能补偿系统的切换成本需要好好评估。

 

两种的方式的组合

组合的方式可以由四种,分别是:同步阻塞、同步非阻塞、异步阻塞、异步非阻塞,这四种方式都对 I/O 性能有影响。虽然异步和非阻塞能够提升 I/O 的性能,但是也会带来一些额外的性能成本,例如会增加线程数量从而增加 CPU 的消耗,同时也会导致程序设计的复杂度上升。如果设计的不合理的话反而会导致性能下降。

 

参考链接:http://www.cnblogs.com/zedosu/p/6666984.html

同步与异步

所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。而异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。我们可以用打电话和发短信来很好的比喻同步与异步操作。

在设计到 IO 处理时通常都会遇到一个是同步还是异步的处理方式的选择问题。因为同步与异步的 I/O 处理方式对调用者的影响很大,在数据库产品中都会遇到这个问题。因为 I/O 操作通常是一个非常耗时的操作,在一个任务序列中 I/O 通常都是性能瓶颈。但是同步与异步的处理方式对程序的可靠性影响非常大,同步能够保证程序的可靠性,而异步可以提升程序的性能,必须在可靠性和性能之间做个平衡,没有完美的解决办法。

阻塞与非阻塞

阻塞与非阻塞主要是从 CPU 的消耗上来说的,阻塞就是 CPU 停下来等待一个慢的操作完成 CPU 才接着完成其它的事。非阻塞就是在这个慢的操作在执行时 CPU 去干其它别的事,等这个慢的操作完成时,CPU 再接着完成后续的操作。虽然表面上看非阻塞的方式可以明显的提高 CPU 的利用率,但是也带了另外一种后果就是系统的线程切换增加。增加的 CPU 使用时间能不能补偿系统的切换成本需要好好评估。

两种的方式的组合

组合的方式可以由四种,分别是:同步阻塞、同步非阻塞、异步阻塞、异步非阻塞,这四种方式都对 I/O 性能有影响。下面给出分析,并有一些常用的设计用例参考。

表 3. 四种组合方式

虽然异步和非阻塞能够提升 I/O 的性能,但是也会带来一些额外的性能成本,例如会增加线程数量从而增加 CPU 的消耗,同时也会导致程序设计的复杂度上升。如果设计的不合理的话反而会导致性能下降。在实际设计时要根据应用场景综合评估一下。

下面举一些异步和阻塞的操作实例:

在 Cassandra 中要查询数据通常会往多个数据节点发送查询命令,但是要检查每个节点返回数据的完整性,所以需要一个异步查询同步结果的应用场景,部分代码如下:

清单 3.异步查询同步结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class AsyncResult implements IAsyncResult{
   private byte[] result_;
   private AtomicBoolean done_ = new AtomicBoolean(false);
   private Lock lock_ = new ReentrantLock();
   private Condition condition_;
   private long startTime_;
   public AsyncResult(){       
       condition_ = lock_.newCondition();// 创建一个锁
       startTime_ = System.currentTimeMillis();
   }   
/*** 检查需要的数据是否已经返回,如果没有返回阻塞 */
public byte[] get(){
       lock_.lock();
       try{
           if (!done_.get()){condition_.await();}
       }catch (InterruptedException ex){
           throw new AssertionError(ex);
       }finally{lock_.unlock();}
       return result_;
}
/*** 检查需要的数据是否已经返回 */
   public boolean isDone(){return done_.get();}
/*** 检查在指定的时间内需要的数据是否已经返回,如果没有返回抛出超时异常 */
   public byte[] get(long timeout, TimeUnit tu) throws TimeoutException{
       lock_.lock();
       try{            boolean bVal = true;
           try{
               if ( !done_.get() ){
          long overall_timeout = timeout - (System.currentTimeMillis() - startTime_);
                   if(overall_timeout > 0)// 设置等待超时的时间
                       bVal = condition_.await(overall_timeout, TimeUnit.MILLISECONDS);
                   else bVal = false;
               }
           }catch (InterruptedException ex){
               throw new AssertionError(ex);
           }
           if ( !bVal && !done_.get() ){// 抛出超时异常
               throw new TimeoutException("Operation timed out.");
           }
       }finally{lock_.unlock();      }
       return result_;
}
/*** 该函数拱另外一个线程设置要返回的数据,并唤醒在阻塞的线程 */
   public void result(Message response){       
       try{
           lock_.lock();
           if ( !done_.get() ){               
               result_ = response.getMessageBody();// 设置返回的数据
               done_.set(true);
               condition_.signal();// 唤醒阻塞的线程
           }
       }finally{lock_.unlock();}       
   }   
}

总结

本文阐述的内容较多,从 Java 基本 I/O 类库结构开始说起,主要介绍了磁盘 I/O 和网络 I/O 的基本工作方式,最后介绍了关于 I/O 调优的一些方法。

 

相关主题

  • 查看文章 《深入分析Java中文编码问题》(developerWorks,2011 年 7 月):详细介绍 Java 中编码问题出现的根本原因,你将了解到:Java 中经常遇到的几种编码格式的区别;Java 中经常需要编码的场景;出现中文问题的原因分析;在开发 Java web 程序时可能会存在编码的几个地方,一个 HTTP 请求怎么控制编码格式?如何避免出现中文问题?
  • 《Oracle JDBC内存管理》:这里详细分析 Oracle JDBC 内存管理的处理方式。
  • 《Jetty 的工作原理和与 Tomcat 的比较》:这里介绍了 Jetty 是如何使用 NIO 技术处理 HTTP 连接请求的,以及与 Tomcat 处理有何不同之处。
  • Java I/O Performance:sun.com 上的文章,介绍了一些 I/O 调优的基本方法。
  • Java NIO.2:这里介绍了 JDK7 里面的新的 I/O 技术,可以参考学习下。
  • Understanding Disk I/O:这里介绍了一点关于磁盘 I/O 一些检测和调优方法,本文也引用了一些知识点。
  • developerWorks Java 技术专区:这里有数百篇关于 Java 编程各个方面的文章。
 

评论

添加或订阅评论,请先登录或注册。

 
  • 最新的
  • 历史
  • 热门
  • 技术分享2017 年 06 月 15 日
    java_worker
    好文啊  咋关注不了作者 
  • 技术分享2016 年 11 月 24 日
    ITriangle
    select函数既视感
  • 技术分享2016 年 10 月 21 日
    徐先森
    不错
  • 技术分享2014 年 12 月 24 日
    javajoker
    楼主讲的不错,IBM提供的平台也不错
  • 技术分享2013 年 12 月 09 日
    charliu
    总结得很好,感谢分享
  • 技术分享2013 年 12 月 04 日
    qmz
    "请问一下,博主的类图是用什么工具画的呢

Java I/O 工作机制(二) —— Java 的 I/O 的交互方式分析