本篇內(nèi)容主要講解“Java I/O API性能實(shí)例分析”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Java I/O API性能實(shí)例分析”吧!
創(chuàng)新互聯(lián)公司2013年開創(chuàng)至今,先為大同等服務(wù)建站,大同等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為大同企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。 一、概述
IO API的可伸縮性對(duì)Web應(yīng)用有著極其重要的意義。Java 1.4版以前的API中,阻塞I/O令許多人失望。從J2SE
1.4版本開始,Java終于有了可伸縮的I/O API。本文分析并計(jì)算了新舊IO
API在可伸縮性方面的差異。Java向Socket寫入數(shù)據(jù)時(shí)必須調(diào)用關(guān)聯(lián)的OutputStream的write()方法。只有當(dāng)所有的數(shù)據(jù)全部寫入時(shí),write()方法調(diào)用才會(huì)返回。倘若發(fā)送緩沖區(qū)已滿且連接速度很低,這個(gè)調(diào)用可能需要一段時(shí)間才能完成。如果程序只使用單一的線程,其他連接就必須等待,即使那些連接已經(jīng)做好了調(diào)用write()的準(zhǔn)備也一樣。為了解決這個(gè)問題,你必須把每一個(gè)Socket和一個(gè)線程關(guān)聯(lián)起來;采用這種方法之后,當(dāng)一個(gè)線程由于I/O相關(guān)的任務(wù)被阻塞時(shí),另一個(gè)線程仍舊能夠運(yùn)行。
盡管線程的開銷不如進(jìn)程那么大,但是,考慮到底層的操作平臺(tái),線程和進(jìn)程都屬于消耗大量資源的程序結(jié)構(gòu)。每一個(gè)線程都要占用一定數(shù)量的內(nèi)存,而且除此之外,多個(gè)線程還意味著線程上下文的切換,而這種切換也需要昂貴的資源開銷。因此,Java需要一個(gè)新的API來分離Socket與線程之間過于緊密的聯(lián)系。在新的Java
I/O API(java.nio.*)中,這個(gè)目標(biāo)終于實(shí)現(xiàn)了。
本文分析和比較了用新、舊兩種I/O
API編寫的簡(jiǎn)單Web服務(wù)器。由于作為Web協(xié)議的HTTP不再象原來那樣只用于一些簡(jiǎn)單的目的,因此這里介紹的例子只包含關(guān)鍵的功能,或者說,它們既不考慮安全因素,也不嚴(yán)格遵從協(xié)議規(guī)范。
二、用舊API編寫的HTTP服務(wù)器
首先我們來看看用舊式API編寫的HTTP服務(wù)器。這個(gè)實(shí)現(xiàn)只使用了一個(gè)類。main()方法首先創(chuàng)建了一個(gè)綁定到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對(duì)象,并用共享的ServerSocket初始化它們。在Httpd的構(gòu)造函數(shù)中,我們保證每一個(gè)實(shí)例都有一個(gè)有意義的名字,設(shè)置默認(rèn)協(xié)議,然后通過調(diào)用其超類Thread的start()方法啟動(dòng)服務(wù)器。此舉導(dǎo)致對(duì)run()方法的一次異步調(diào)用,而run()方法包含一個(gè)無限循環(huán)。
在run()方法的無限循環(huán)中,ServerSocket的阻塞性accpet()方法被調(diào)用。當(dāng)客戶程序連接服務(wù)器的8080端口,accept()方法將返回一個(gè)Socket對(duì)象。每一個(gè)Socket關(guān)聯(lián)著一個(gè)InputStream和一個(gè)OutputStream,兩者都要在后繼的handleRequest()方法調(diào)用中用到。這個(gè)方法將讀取客戶程序的請(qǐng)求,經(jīng)過檢查和處理,然后把合適的應(yīng)答發(fā)送給客戶程序。如果客戶程序的請(qǐng)求合法,通過sendFile()方法返回客戶程序請(qǐng)求的文件;否則,客戶程序?qū)⑹盏较鄳?yīng)的錯(cuò)誤信息(調(diào)用sendError())方法。
while (true) {
...
socket =
serverSocket.accept();
...
handleRequest();
...
socket.close();
}
現(xiàn)在我們來分析一下這個(gè)實(shí)現(xiàn)。它能夠出色地完成任務(wù)嗎?答案基本上是肯定的。當(dāng)然,請(qǐng)求分析過程還可以進(jìn)一步優(yōu)化,因?yàn)樵谛阅芊矫鍿tringTokenizer的聲譽(yù)一直不佳。但這個(gè)程序至少已經(jīng)關(guān)閉了TCP延遲(對(duì)于短暫的連接來說它很不合適),同時(shí)為外發(fā)的文件設(shè)置了緩沖。而且更重要的是,所有的線程操作都相互獨(dú)立。新的連接請(qǐng)求由哪一個(gè)線程處理由本機(jī)的(因而也是速度較快的)accept()方法決定。除了ServerSocket對(duì)象之外,各個(gè)線程之間不共享可能需要同步的任何其他資源。這個(gè)方案速度較快,但令人遺憾的是,它不具有很好的可伸縮性,其原因就在于,很顯然地,線程是一種有限的資源。
三、非阻塞的HTTP服務(wù)器
下面我們來看看另一個(gè)使用非阻塞的新I/O
API的方案。新的方案要比原來的方案稍微復(fù)雜一點(diǎn),而且它需要各個(gè)線程的協(xié)作。它包含下面四個(gè)類:
?NIOHttpd
?Acceptor
?Connection
?ConnectionSelector
NIOHttpd的主要任務(wù)是啟動(dòng)服務(wù)器。就象前面的Httpd一樣,一個(gè)服務(wù)器Socket被綁定到8080端口。兩者主要的區(qū)別在于,新版本的服務(wù)器使用java.nio.channels.ServerSocketChannel而不是ServerSocket。在利用bind()方法顯式地把Socket綁定到端口之前,必須先打開一個(gè)管道(Channel)。然后,main()方法實(shí)例化了一個(gè)ConnectionSelector和一個(gè)Acceptor。這樣,每一個(gè)ConnectionSelector都可以用一個(gè)Acceptor注冊(cè);另外,實(shí)例化Acceptor時(shí)還提供了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);
}
為了理解這兩個(gè)線程之間的交互過程,首先我們來仔細(xì)地分析一下Acceptor。Acceptor的主要任務(wù)是接受傳入的連接請(qǐng)求,并通過ConnectionSelector注冊(cè)它們。Acceptor的構(gòu)造函數(shù)調(diào)用了超類的start()方法;run()方法包含了必需的無限循環(huán)。在這個(gè)循環(huán)中,一個(gè)阻塞性的accept()方法被調(diào)用,它最終將返回一個(gè)Socket對(duì)象??這個(gè)過程幾乎與Httpd的處理過程一樣,但這里使用的是ServerSocketChannel的accept()方法,而不是ServerSocket的accept()方法。最后,以調(diào)用accept()方法獲得的socketChannel對(duì)象為參數(shù)創(chuàng)建一個(gè)Connection對(duì)象,并通過ConnectionSelector的queue()方法注冊(cè)它。
while (true) {
...
socketChannel =
serverSocketChannel.accept();
connectionSelector.queue(new
Connection(socketChannel));
...
}
總而言之:Acceptor只能在一個(gè)無限循環(huán)中接受連接請(qǐng)求和通過ConnectionSelector注冊(cè)連接。與Acceptor一樣,ConnectionSelector也是一個(gè)線程。在構(gòu)造函數(shù)中,它構(gòu)造了一個(gè)隊(duì)列,并用Selector.open()方法打開了一個(gè)java.nio.channels.Selector。Selector是整個(gè)服務(wù)器中最重要的部分之一,它使得程序能夠注冊(cè)連接,能夠獲取已經(jīng)允許讀取和寫入操作的連接的清單。
構(gòu)造函數(shù)調(diào)用start()方法之后,run()方法里面的無限循環(huán)開始執(zhí)行。在這個(gè)循環(huán)中,程序調(diào)用了Selector的select()方法。這個(gè)方法一直阻塞,直到已經(jīng)注冊(cè)的連接之一做好了I/O操作的準(zhǔn)備,或Selector的wakeup()方法被調(diào)用。
while (true) {
...
int i =
selector.select();
registerQueuedConnections();
...
//
處理連接...
}
當(dāng)ConnectionSelector線程執(zhí)行select()時(shí),沒有一個(gè)Acceptor線程能夠用該Selector注冊(cè)連接,因?yàn)閷?duì)應(yīng)的方法是同步方法,理解這一點(diǎn)是很重要的。因此這里使用了隊(duì)列,必要時(shí)Acceptor線程向隊(duì)列加入連接。
public void queue(Connection connection) {
synchronized
(queue) {
queue.add(connection);
}
selector.wakeup();
}
緊接著把連接放入隊(duì)列的操作,Acceptor調(diào)用Selector的wakeup()方法。這個(gè)調(diào)用導(dǎo)致ConnectionSelector線程繼續(xù)執(zhí)行,從正在被阻塞的select()調(diào)用返回。由于Selector不再被阻塞,ConnectionSelector現(xiàn)在能夠從隊(duì)列注冊(cè)連接。在registerQueuedConnections()方法中,其實(shí)施過程如下:
if (!queue.isEmpty()) {
synchronized (queue) {
while
(!queue.isEmpty()) {
Connection connection
=
(Connection)queue.remove(queue.size()-1);
connection.register(selector);
}
}
}
到此,相信大家對(duì)“Java I/O API性能實(shí)例分析”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
網(wǎng)站欄目:JavaI/OAPI性能實(shí)例分析-創(chuàng)新互聯(lián)
網(wǎng)頁路徑:http://muchs.cn/article34/cdjppe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供商城網(wǎng)站、網(wǎng)頁設(shè)計(jì)公司、電子商務(wù)、域名注冊(cè)、網(wǎng)站維護(hù)、品牌網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容