国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

JavaI/OAPI之性能分析(上)

2019-11-18 11:16:16
字體:
供稿:網(wǎng)友

  IO API的可伸縮性對Web應(yīng)用有著極其重要的意義。java 1.4版以前的API中,阻塞I/O令許多人失望。從J2SE 1.4版本開始,Java終于有了可伸縮的I/O API。本文分析并計算了新舊I/O API在可伸縮性方面的差異。
  
  一、概述
  IO API的可伸縮性對Web應(yīng)用有著極其重要的意義。Java 1.4版以前的API中,阻塞I/O令許多人失望。從J2SE 1.4版本開始,Java終于有了可伸縮的I/O API。本文分析并計算了新舊IO API在可伸縮性方面的差異。Java向Socket寫入數(shù)據(jù)時必須調(diào)用關(guān)聯(lián)的OutputStream的write()方法。只有當(dāng)所有的數(shù)據(jù)全部寫入時,write()方法調(diào)用才會返回。倘若發(fā)送緩沖區(qū)已滿且連接速度很低,這個調(diào)用可能需要一段時間才能完成。假如程序只使用單一的線程,其他連接就必須等待,即使那些連接已經(jīng)做好了調(diào)用write()的預(yù)備也一樣。為了解決這個問題,你必須把每一個Socket和一個線程關(guān)聯(lián)起來;采用這種方法之后,當(dāng)一個線程由于I/O相關(guān)的任務(wù)被阻塞時,另一個線程仍然能夠運行。
  
  盡管線程的開銷不如進(jìn)程那么大,但是,考慮到底層的操作平臺,線程和進(jìn)程都屬于消耗大量資源的程序結(jié)構(gòu)。每一個線程都要占用一定數(shù)量的內(nèi)存,而且除此之外,多個線程還意味著線程上下文的切換,而這種切換也需要昂貴的資源開銷。因此,Java需要一個新的API來分離Socket與線程之間過于緊密的聯(lián)系。在新的Java I/O API(java.nio.*)中,這個目標(biāo)終于實現(xiàn)了。
  
  本文分析和比較了用新、舊兩種I/O API編寫的簡單Web服務(wù)器。由于作為Web協(xié)議的HTTP不再象原來那樣只用于一些簡單的目的,因此這里介紹的例子只包含要害的功能,或者說,它們既不考慮安全因素,也不嚴(yán)格遵從協(xié)議規(guī)范。
  
  二、用舊API編寫的HTTP服務(wù)器
  首先我們來看看用舊式API編寫的HTTP服務(wù)器。這個實現(xiàn)只使用了一個類。main()方法首先創(chuàng)建了一個綁定到8080端口的ServerSocket:
  
  public static void main() throws IOException {
  ServerSocket serverSocket = new ServerSocket(8080);
  for (int i=0; i < Integer.parseInt(args[0]); i++) {
  new Httpd(serverSocket);
  }
  }
  
  接下來,main()方法創(chuàng)建了一系列的Httpd對象,并用共享的ServerSocket初始化它們。在Httpd的構(gòu)造函數(shù)中,我們保證每一個實例都有一個有意義的名字,設(shè)置默認(rèn)協(xié)議,然后通過調(diào)用其超類Thread的start()方法啟動服務(wù)器。此舉導(dǎo)致對run()方法的一次異步調(diào)用,而run()方法包含一個無限循環(huán)。
  
  在run()方法的無限循環(huán)中,ServerSocket的阻塞性accpet()方法被調(diào)用。當(dāng)客戶程序連接服務(wù)器的8080端口,accept()方法將返回一個Socket對象。每一個Socket關(guān)聯(lián)著一個InputStream和一個OutputStream,兩者都要在后繼的handleRequest()方法調(diào)用中用到。這個方法將讀取客戶程序的請求,經(jīng)過檢查和處理,然后把合適的應(yīng)答發(fā)送給客戶程序。假如客戶程序的請求合法,通過sendFile()方法返回客戶程序請求的文件;否則,客戶程序?qū)⑹盏较鄳?yīng)的錯誤信息(調(diào)用sendError())方法。
  
  
  while (true) {
  ...
  socket = serverSocket.accept();
  ...
  handleRequest();
  ...
  socket.close();
  }
  
  現(xiàn)在我們來分析一下這個實現(xiàn)。它能夠出色地完成任務(wù)嗎?答案基本上是肯定的。當(dāng)然,請求分析過程還可以進(jìn)一步優(yōu)化,因為在性能方面StringTokenizer的聲譽一直不佳。但這個程序至少已經(jīng)關(guān)閉了TCP延遲(對于短暫的連接來說它很不合適),同時為外發(fā)的文件設(shè)置了緩沖。而且更重要的是,所有的線程操作都相互獨立。新的連接請求由哪一個線程處理由本機(jī)的(因而也是速度較快的)accept()方法決定。除了ServerSocket對象之外,各個線程之間不共享可能需要同步的任何其他資源。這個方案速度較快,但令人遺憾的是,它不具有很好的可伸縮性,其原因就在于,很顯然地,線程是一種有限的資源。
  
  三、非阻塞的HTTP服務(wù)器
  下面我們來看看另一個使用非阻塞的新I/O API的方案。新的方案要比原來的方案稍微復(fù)雜一點,而且它需要各個線程的協(xié)作。它包含下面四個類:
  
  NIOHttpd
  Acceptor
  Connection
  ConnectionSelector
  
  NIOHttpd的主要任務(wù)是啟動服務(wù)器。就象前面的Httpd一樣,一個服務(wù)器Socket被綁定到8080端口。兩者主要的區(qū)別在于,新版本的服務(wù)器使用java.nio.channels.ServerSocketChannel而不是ServerSocket。在利用bind()方法顯式地把Socket綁定到端口之前,必須先打開一個管道(Channel)。然后,main()方法實例化了一個ConnectionSelector和一個Acceptor。這樣,每一個ConnectionSelector都可以用一個Acceptor注冊;另外,實例化Acceptor時還提供了ServerSocketChannel。
  
  public static void main() throws IOException {
  ServerSocketChannel ssc = ServerSocketChannel.open();
  ssc.socket().bind(new InetSocketAddress(8080));
  ConnectionSelector cs = new ConnectionSelector();
  new Acceptor(ssc, cs);
  }
  
  為了理解這兩個線程之間的交互過程,首先我們來仔細(xì)地分析一下Acceptor。Acceptor的主要任務(wù)是接受傳入的連接請求,并通過ConnectionSelector注冊它們。Acceptor的構(gòu)造函數(shù)調(diào)用了超類的start()方法;run()方法包含了必需的無限循環(huán)。在這個循環(huán)中,一個阻塞性的accept()方法被調(diào)用,它最終將返回一個Socket對象――這個過程幾乎與Httpd的處理過程一樣,但這里使用的是ServerSocketChannel的accept()方法,而不是ServerSocket的accept()方法。最后,以調(diào)用accept()方法獲得的socketChannel對象為參數(shù)創(chuàng)建一個Connection對象,并通過ConnectionSelector的queue()方法注冊它。
  
  while (true) {
  ...
  socketChannel = serverSocketChannel.accept();
  connectionSelector.queue(new Connection(socketChannel));
  ...
  }
  
  總而言之:Acceptor只能在一個無限循環(huán)中接受連接請求和通過ConnectionSelector注冊連接。與Acceptor一樣,ConnectionSelector也是一個線程。在構(gòu)造函數(shù)中,它構(gòu)造了一個隊列,并用Selector.open()方法打開了一個java.nio.channels.Selector。Selector是整個服務(wù)器中最重要的部分之一,它使得程序能夠注冊連接,能夠獲取已經(jīng)答應(yīng)讀取和寫入操作的連接的清單。
  
  構(gòu)造函數(shù)調(diào)用start()方法之后,run()方法里面的無限循環(huán)開始執(zhí)行。在這個循環(huán)中,程序調(diào)用了Selector的select()方法。這個方法一直阻塞,直到已經(jīng)注冊的連接之一做好了I/O操作的預(yù)備,或Selector的wakeup()方法被調(diào)用。
  
  while (true) {
  ...
  int i = selector.select();
  registerQueuedConnections();
  ...
  // 處理連接...
  }
  
  當(dāng)ConnectionSelector線程執(zhí)行select()時,沒有一個Acceptor線程能夠用該Selector注冊連接,因為對應(yīng)的方法是同步方法,理解這一點是很重要的。因此這里使用了隊列,必要時Acceptor線程向隊列加入連接。
  
  public void queue(Connection connection) {
  synchronized (queue) {
  queue.add(connection);
  }
  selector.wakeup();
  }
  
  緊接著把連接放入隊列的操作,Acceptor調(diào)用Selector的wakeup()方法。這個調(diào)用導(dǎo)致ConnectionSelector線程繼續(xù)執(zhí)行,從正在被阻塞的select()調(diào)用返回。由于Selector不再被阻塞,ConnectionSelector現(xiàn)在能夠從隊列注冊連接。在registerQueuedConnections()方法中,其實施過程如下:
  
  if (!queue.isEmpty()) {
  synchronized (queue) {
  while (!queue.isEmpty()) {
  Connection connection =
  (Connection)queue.remove(queue.size()-1);
  connection.register(selector);
  }
  }
  }

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 新巴尔虎右旗| 嘉兴市| 南通市| 嘉鱼县| 肥城市| 沂源县| 宁武县| 芦山县| 呼和浩特市| 尚义县| 枣强县| 六枝特区| 文登市| 舒城县| 西乡县| 太康县| 大余县| 邳州市| 长白| 荔浦县| 上饶市| 左权县| 乡城县| 毕节市| 苗栗县| 神农架林区| 宝坻区| 富川| 宝兴县| 峨边| 临江市| 庆城县| 南投县| 宁强县| 犍为县| 胶南市| 安西县| 漳平市| 高台县| 松原市| 新巴尔虎左旗|