熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

Java I/O API之性能分析 (上)

2013-11-23 19:44:58  來源: Java高級技術 

  IO API的可伸縮性對Web應用有著極其重要的意義Java 版以前的API中阻塞I/O令許多人失望從JSE 版本開始Java終於有了可伸縮的I/O API本文分析並計算了新舊I/O API在可伸縮性方面的差異
  
  概述
  IO API的可伸縮性對Web應用有著極其重要的意義Java 版以前的API中阻塞I/O令許多人失望從JSE 版本開始Java終於有了可伸縮的I/O API本文分析並計算了新舊IO API在可伸縮性方面的差異Java向Socket寫入數據時必須調用關聯的OutputStream的write()方法只有當所有的數據全部寫入時write()方法調用才會返回倘若發送緩沖區已滿且連接速度很低這個調用可能需要一段時間才能完成如果程序只使用單一的線程其他連接就必須等待即使那些連接已經做好了調用write()的准備也一樣為了解決這個問題你必須把每一個Socket和一個線程關聯起來采用這種方法之後當一個線程由於I/O相關的任務被阻塞時另一個線程仍舊能夠運行
  
  盡管線程的開銷不如進程那麼大但是考慮到底層的操作平台線程和進程都屬於消耗大量資源的程序結構每一個線程都要占用一定數量的內存而且除此之外多個線程還意味著線程上下文的切換而這種切換也需要昂貴的資源開銷因此Java需要一個新的API來分離Socket與線程之間過於緊密的聯系在新的Java I/O API(javanio*)中這個目標終於實現了
  
  本文分析和比較了用新舊兩種I/O API編寫的簡單Web服務器由於作為Web協議的HTTP不再象原來那樣只用於一些簡單的目的因此這裡介紹的例子只包含關鍵的功能或者說它們既不考慮安全因素也不嚴格遵從協議規范
  
  用舊API編寫的HTTP服務器
  首先我們來看看用舊式API編寫的HTTP服務器這個實現只使用了一個類main()方法首先創建了一個綁定到端口的ServerSocket
  
  public static void main() throws IOException {
  ServerSocket serverSocket = new ServerSocket();
  for (int i=; i < IntegerparseInt(args[]); i++) {
  new Httpd(serverSocket);
  }
  }
  
  接下來main()方法創建了一系列的Httpd對象並用共享的ServerSocket初始化它們在Httpd的構造函數中我們保證每一個實例都有一個有意義的名字設置默認協議然後通過調用其超類Thread的start()方法啟動服務器此舉導致對run()方法的一次異步調用而run()方法包含一個無限循環
  
  在run()方法的無限循環中ServerSocket的阻塞性accpet()方法被調用當客戶程序連接服務器的端口accept()方法將返回一個Socket對象每一個Socket關聯著一個InputStream和一個OutputStream兩者都要在後繼的handleRequest()方法調用中用到這個方法將讀取客戶程序的請求經過檢查和處理然後把合適的應答發送給客戶程序如果客戶程序的請求合法通過sendFile()方法返回客戶程序請求的文件否則客戶程序將收到相應的錯誤信息(調用sendError())方法
  
  
  while (true) {
  
  socket = serverSocketaccept();
  
  handleRequest();
  
  socketclose();
  }
  
  現在我們來分析一下這個實現它能夠出色地完成任務嗎?答案基本上是肯定的當然請求分析過程還可以進一步優化因為在性能方面StringTokenizer的聲譽一直不佳但這個程序至少已經關閉了TCP延遲(對於短暫的連接來說它很不合適)同時為外發的文件設置了緩沖而且更重要的是所有的線程操作都相互獨立新的連接請求由哪一個線程處理由本機的(因而也是速度較快的)accept()方法決定除了ServerSocket對象之外各個線程之間不共享可能需要同步的任何其他資源這個方案速度較快但令人遺憾的是它不具有很好的可伸縮性其原因就在於很顯然地線程是一種有限的資源
  
  非阻塞的HTTP服務器
  下面我們來看看另一個使用非阻塞的新I/O API的方案新的方案要比原來的方案稍微復雜一點而且它需要各個線程的協作它包含下面四個類
  
  NIOHttpd
  Acceptor
  Connection
  ConnectionSelector
  
  NIOHttpd的主要任務是啟動服務器就象前面的Httpd一樣一個服務器Socket被綁定到端口兩者主要的區別在於新版本的服務器使用javaniochannelsServerSocketChannel而不是ServerSocket在利用bind()方法顯式地把Socket綁定到端口之前必須先打開一個管道(Channel)然後main()方法實例化了一個ConnectionSelector和一個Acceptor這樣每一個ConnectionSelector都可以用一個Acceptor注冊另外實例化Acceptor時還提供了ServerSocketChannel
  
  public static void main() throws IOException {
  ServerSocketChannel ssc = ServerSocketChannelopen();
  sscsocket()bind(new InetSocketAddress());
  ConnectionSelector cs = new ConnectionSelector();
  new Acceptor(ssc cs);
  }
  
  為了理解這兩個線程之間的交互過程首先我們來仔細地分析一下AcceptorAcceptor的主要任務是接受傳入的連接請求並通過ConnectionSelector注冊它們Acceptor的構造函數調用了超類的start()方法run()方法包含了必需的無限循環在這個循環中一個阻塞性的accept()方法被調用它最終將返回一個Socket對象——這個過程幾乎與Httpd的處理過程一樣但這裡使用的是ServerSocketChannel的accept()方法而不是ServerSocket的accept()方法最後以調用accept()方法獲得的socketChannel對象為參數創建一個Connection對象並通過ConnectionSelector的queue()方法注冊它
  
  while (true) {
  
  socketChannel = serverSocketChannelaccept();
  connectionSelectorqueue(new Connection(socketChannel));
  
  }
  
  總而言之Acceptor只能在一個無限循環中接受連接請求和通過ConnectionSelector注冊連接與Acceptor一樣ConnectionSelector也是一個線程在構造函數中它構造了一個隊列並用Selectoropen()方法打開了一個javaniochannelsSelectorSelector是整個服務器中最重要的部分之一它使得程序能夠注冊連接能夠獲取已經允許讀取和寫入操作的連接的清單
  
  構造函數調用start()方法之後run()方法裡面的無限循環開始執行在這個循環中程序調用了Selector的select()方法這個方法一直阻塞直到已經注冊的連接之一做好了I/O操作的准備或Selector的wakeup()方法被調用
  
  while (true) {
  
  int i = selectorselect();
  registerQueuedConnections();
  
  // 處理連接
  }
  
  當ConnectionSelector線程執行select()時沒有一個Acceptor線程能夠用該Selector注冊連接因為對應的方法是同步方法理解這一點是很重要的因此這裡使用了隊列必要時Acceptor線程向隊列加入連接
  
  public void queue(Connection connection) {
  synchronized (queue) {
  queueadd(connection);
  }
  selectorwakeup();
  }
  
  緊接著把連接放入隊列的操作Acceptor調用Selector的wakeup()方法這個調用導致ConnectionSelector線程繼續執行從正在被阻塞的select()調用返回由於Selector不再被阻塞ConnectionSelector現在能夠從隊列注冊連接在registerQueuedConnections()方法中其實施過程如下
  
  if (!queueisEmpty()) {
  synchronized (queue) {
  while (!queueisEmpty()) {
  Connection connection =
  (Connection)queueremove(queuesize());
  connectionregister(selector);
  }
  }
  }
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27392.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.