蘇州培訓(xùn)網(wǎng) > 蘇州JAVA培訓(xùn)機(jī)構(gòu) > 蘇州其然軟件開發(fā)培訓(xùn)
首頁 培訓(xùn)網(wǎng) 最新資訊 熱門問答

蘇州其然軟件開發(fā)培訓(xùn)

免費(fèi)試聽 13013833891

您當(dāng)前的位置: 資訊首頁 > JAVA培訓(xùn)資訊 > 蘇州學(xué)java好么_蘇州JAVA培訓(xùn)

蘇州學(xué)java好么_蘇州JAVA培訓(xùn)

來源:教育聯(lián)展網(wǎng)    編輯:佚名    發(fā)布時間:2018-08-21

蘇州學(xué)java好么
其然IT公司簡介

其然IT教育科技有限公司,是一家專注于培養(yǎng)高級IT技術(shù) 人才,為學(xué)員提供定制化IT職業(yè)規(guī)劃方案及意見咨詢服務(wù)的教育科技公司。秉著“全心全意為學(xué)員服務(wù),認(rèn)認(rèn)真真做好教育工作,凡事多做一點(diǎn),別太 計較得失”的企業(yè)文化,立志打造一個教學(xué)專業(yè)并緊跟當(dāng)下流行前沿技術(shù),讓學(xué)員保持強(qiáng)有力的核心競爭力、在企業(yè)中具有真正實(shí)戰(zhàn)能力,讓更多有夢 想的年輕人學(xué)到實(shí)實(shí)在在有價值的知識,讓知識真正改變命運(yùn)的IT培訓(xùn)品牌。公司CEO不僅是明星講師亦是知名獨(dú)立框架開發(fā)者,其開發(fā)的2個世界通用 流行開源框架(MJRefresh、MJExtension),曾經(jīng)在蘋果開發(fā)github排名OC語言分類中領(lǐng)導(dǎo)全球排名。因其獨(dú)特的個人魅力及一貫的堅守吸引了眾多實(shí)戰(zhàn) 派講師紛紛加入,共同構(gòu)造了小碼哥與時俱進(jìn)的教研團(tuán)隊(duì)。其然IT教育自成立以來碩果累累:陸續(xù)開設(shè)6大學(xué)科;累計培訓(xùn)幾千名學(xué)員高薪就業(yè);曾為 中國中央電視臺CCTV發(fā)現(xiàn)之旅《華商論見》欄目組特約戰(zhàn)略合作伙伴,雙方在傳媒、網(wǎng)絡(luò)媒體、人才輸送等方面擁有著廣泛的合作。2015年下旬,其然 IT教育憑借其優(yōu)良的口碑榮獲“2015中國IT教育培訓(xùn)行業(yè)**具影響力領(lǐng)軍品牌”、“2015年度互聯(lián)網(wǎng)責(zé)任品牌”等多項(xiàng)大獎。

蘇州學(xué)java好么

java入門要注意什么

蘇州學(xué)java好么

學(xué)習(xí)java就像是一個種花的過程,不斷地為其施肥澆水,它才會茁壯成長。 而我們學(xué)習(xí)java,就要不斷的充實(shí)自己、提升自己,才能獲得更多機(jī)會。很多開始學(xué)習(xí)java編程的小白,經(jīng)常就會被概念、定義什么的搞糊涂。當(dāng)分類 、對象、接口、構(gòu)造函數(shù)等等各種專業(yè)名詞出現(xiàn)的時候,你一定是腦子里好像一片空白,根本就搞不懂這些字眼的意思和關(guān)系,而且,這種情況下,很 容易導(dǎo)致你喪失自信心,開始逃避、拒絕,這些小白經(jīng)常遇到的情況在我剛接觸java的時候也遇見了,但是好在我足夠幸運(yùn),遇見了誠筑說。我現(xiàn)在已 經(jīng)是公司的項(xiàng)目經(jīng)理了,今天,我為大家來總結(jié)了一些經(jīng)驗(yàn)和建議,希望能夠幫助到大家。

一點(diǎn):熟練基本的j2seAPI

除去java語言本身的語法之外呢,要懂得并且熟練j2seAPI的API也是非常有 必要的,在這里,就建議大家首先去掌握字符串的處理、異常的處理、容器、輸入輸出、線程等,這些相對來說較為重要的。還有就是API的內(nèi)容是非 常龐大的,關(guān)于API,一定要懂得查詢API的文件說明,在了解了其作用用途或者目的才能夠進(jìn)行相對于的程序。

二點(diǎn):穩(wěn)固java的語法基礎(chǔ)

學(xué)習(xí)java一定要學(xué)會使用java的程序語言,用來編寫程序,但是學(xué)習(xí)程序語 言就要熟悉語法是怎么使用的。程序語言其實(shí)也是一種語言,不過跟人類的語言不同,這種語言是要和計算機(jī)溝通交流,那怎么做才能熟悉這種語言呢 ,我給出的建議是多看別人寫的程序,了解人家是怎么用java來解決問題的。然后再找類似的程序去練習(xí)了,這樣就能夠從實(shí)際操作中檢驗(yàn)自己是否真 的知道該怎么去解決問題了。

三點(diǎn):加入貼吧論壇多參與討論

根據(jù)我當(dāng)時的經(jīng)驗(yàn),在大家學(xué)習(xí)的過程中,如果有人可以參與話題,共同討 論的話,會加快你學(xué)習(xí)的速度。所以大家可以和我一樣,找一個技術(shù)討論的地方,貼吧啊,論壇啊都可以,在這里進(jìn)行討論,畢竟大家有著共同的目標(biāo) 和理想,有著共同的話題可聊,這樣的話,又大大節(jié)省了學(xué)習(xí)的時間。

學(xué)完基本的java語法呢,現(xiàn)在就該用java來進(jìn)行實(shí)際的編程了,假如你需要 編寫窗口程序,那就學(xué)Swing窗口設(shè)計;假如你要編寫數(shù)據(jù)庫什么的,那就學(xué)JDBC等等。

Java開發(fā)體系結(jié)構(gòu)介紹

蘇州學(xué)java好么

Java開發(fā)體系結(jié)構(gòu)介紹

1、類加載器:為程序的執(zhí)行加載所需要的全部類。類加載器將本地文件系 統(tǒng)的類名空間與來自遠(yuǎn)程網(wǎng)絡(luò)源的類名空間相分離,本地類總是首先被加載,以增加安全性。當(dāng)全部類被加載后,可執(zhí)行文件的存儲器格式被確定。這 時,特定的存儲器地址被分配給符號引用并創(chuàng)建檢索表格。由于存儲器格式在運(yùn)行時出現(xiàn),因而Java解釋器增加了保護(hù)以防止對限制代碼區(qū)的非法進(jìn)入 。

2、字節(jié)代碼校驗(yàn)器:基于代碼的規(guī)范包括語法語義的檢查以及如上所述的 安全性檢查。

3、Java運(yùn)行時解釋器:它是JVM的核心內(nèi)容,實(shí)現(xiàn)把抽象的字節(jié)碼指令映射 到本地系統(tǒng)平臺下的庫引用或指令。

4、API類庫:實(shí)現(xiàn)標(biāo)準(zhǔn)Java平臺API的一系列可執(zhí)行代碼。

5、硬件本地平臺接口:提供對底層系統(tǒng)平臺資源庫調(diào)用的接口。

Java NIO淺析


>

原文地址:http://tech.meituan.com/nio.html

NIO(Non-blocking I/O,在java領(lǐng)域,也稱為New I/O),是一種同步非阻塞的I/O模型,也是I/O多路復(fù)用的基礎(chǔ),已經(jīng)被越來越多地應(yīng)用到大型應(yīng)用服務(wù)器,成為解決高并發(fā)與大量連接、I/O處理問題的有效方式。

那么NIO的本質(zhì)是什么樣的呢?它是怎樣與事件模型結(jié)合來解放線程、提高系統(tǒng)吞吐的呢?

本文會從傳統(tǒng)的阻塞I/O和線程池模型面臨的問題講起,然后對比幾種常見I/O模型,一步步分析NIO怎么利用事件模型處理I/O,解決線程池瓶頸處理海量連接,包括利用面向事件的方式編寫服務(wù)端/客戶端程序。**后延展到一些高級主題,如Reactor與PRoactor模型的對比、Selector的喚醒、Buffer的選擇等。

注:本文的代碼都是偽代碼,主要是為了示意,不可用于生產(chǎn)環(huán)境。

傳統(tǒng)BIO模型分析

讓我們先回憶一下傳統(tǒng)的服務(wù)器端同步阻塞I/O處理(也就是BIO,Blocking I/O)的經(jīng)典編程模型:

{
 ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//線程池
 ServerSocket serverSocket = new ServerSocket();
 serverSocket.bind(8088);
 while(!Thread.currentThread.isInturrupted()){//主線程死循環(huán)等待新連接到來
 Socket socket = serverSocket.accept();
 executor.submit(new ConnectIOnHandler(socket));//為新的連接創(chuàng)建新的線程
}
class ConnectIOnHandler extends Thread{
    private Socket socket;
    public ConnectIOnHandler(Socket socket){
       this.socket = socket;
    }
    public void run(){
      while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){死循環(huán)處理讀寫事件
          String someThing = socket.read()....//讀取數(shù)據(jù)
          if(someThing!=null){
             ......//處理數(shù)據(jù)
             socket.write()....//寫數(shù)據(jù)
          }
      }
    }
}

這是一個經(jīng)典的每連接每線程的模型,之所以使用多線程,主要原因在于socket.accept()、socket.read()、socket.write()三個主要函數(shù)都是同步阻塞的,當(dāng)一個連接在處理I/O的時候,系統(tǒng)是阻塞的,如果是單線程的話必然就掛死在那里;但CPU是被釋放出來的,開啟多線程,就可以讓CPU去處理更多的事情。其實(shí)這也是所有使用多線程的本質(zhì):

利用多核。當(dāng)I/O阻塞系統(tǒng),但CPU空閑的時候,可以利用多線程使用CPU資源。

現(xiàn)在的多線程一般都使用線程池,可以讓線程的創(chuàng)建和回收成本相對較低。在活動連接數(shù)不是特別高(小于單機(jī)1000)的情況下,這種模型是比較不錯的,可以讓每一個連接專注于自己的I/O并且編程模型簡單,也不用過多考慮系統(tǒng)的過載、限流等問題。線程池本身就是一個天然的漏斗,可以緩沖一些系統(tǒng)處理不了的連接或請求。

不過,這個模型**本質(zhì)的問題在于,嚴(yán)重依賴于線程。但線程是很"貴"的資源,主要表現(xiàn)在:

線程的創(chuàng)建和銷毀成本很高,在linux這樣的操作系統(tǒng)中,線程本質(zhì)上就是一個進(jìn)程。創(chuàng)建和銷毀都是重量級的系統(tǒng)函數(shù)。線程本身占用較大內(nèi)存,像Java的線程棧,一般至少分配512K~1M的空間,如果系統(tǒng)中的線程數(shù)過千,恐怕整個JVM的內(nèi)存都會被吃掉一半。線程的切換成本是很高的。操作系統(tǒng)發(fā)生線程切換的時候,需要保留線程的上下文,然后執(zhí)行系統(tǒng)調(diào)用。如果線程數(shù)過高,可能執(zhí)行線程切換的時間甚至?xí)笥诰€程執(zhí)行的時間,這時候帶來的表現(xiàn)往往是系統(tǒng)load偏高、CPU sy使用率特別高(超過20%以上),導(dǎo)致系統(tǒng)幾乎陷入不可用的狀態(tài)。容易造成鋸齒狀的系統(tǒng)負(fù)載。因?yàn)橄到y(tǒng)負(fù)載是用活動線程數(shù)或CPU核心數(shù),一旦線程數(shù)量高但外部網(wǎng)絡(luò)環(huán)境不是很穩(wěn)定,就很容易造成大量請求的結(jié)果同時返回,激活大量阻塞線程從而使系統(tǒng)負(fù)載壓力過大。

所以,當(dāng)面對十萬甚至百萬級連接的時候,傳統(tǒng)的BIO模型是無能為力的。隨著移動端應(yīng)用的興起和各種網(wǎng)絡(luò)游戲的盛行,百萬級長連接日趨普遍,此時,必然需要一種更高效的I/O處理模型。

NIO是怎么工作的

很多剛接觸NIO的人,**眼看到的就是Java相對晦澀的API,比如:Channel,Selector,Socket什么的;然后就是一坨上百行的代碼來演示NIO的服務(wù)端Demo……瞬間頭大有沒有?

我們不管這些,拋開現(xiàn)象看本質(zhì),先分析下NIO是怎么工作的。

常見I/O模型對比

所有的系統(tǒng)I/O都分為兩個階段:等待就緒和操作。舉例來說,讀函數(shù),分為等待系統(tǒng)可讀和真正的讀;同理,寫函數(shù)分為等待網(wǎng)卡可以寫和真正的寫。

需要說明的是等待就緒的阻塞是不使用CPU的,是在“空等”;而真正的讀寫操作的阻塞是使用CPU的,真正在"干活",而且這個過程非???,屬于memory copy,帶寬通常在1GB/s級別以上,可以理解為基本不耗時。

下圖是幾種常見I/O模型的對比:

以socket.read()為例子:

傳統(tǒng)的BIO里面socket.read(),如果TCP RecvBuffer里沒有數(shù)據(jù),函數(shù)會一直阻塞,直到收到數(shù)據(jù),返回讀到的數(shù)據(jù)。

對于NIO,如果TCP RecvBuffer有數(shù)據(jù),就把數(shù)據(jù)從網(wǎng)卡讀到內(nèi)存,并且返回給用戶;反之則直接返回0,永遠(yuǎn)不會阻塞。

**新的AIO(Async I/O)里面會更進(jìn)一步:不但等待就緒是非阻塞的,就連數(shù)據(jù)從網(wǎng)卡到內(nèi)存的過程也是異步的。

換句話說,BIO里用戶**關(guān)心“我要讀”,NIO里用戶**關(guān)心"我可以讀了",在AIO模型里用戶更需要關(guān)注的是“讀完了”。

NIO一個重要的特點(diǎn)是:socket主要的讀、寫、注冊和接收函數(shù),在等待就緒階段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)。

如何結(jié)合事件模型使用NIO同步非阻塞特性

回憶BIO模型,之所以需要多線程,是因?yàn)樵谶M(jìn)行I/O操作的時候,一是沒有辦法知道到底能不能寫、能不能讀,只能"傻等",即使**各種估算,算出來操作系統(tǒng)沒有能力進(jìn)行讀寫,也沒法在socket.read()和socket.write()函數(shù)中返回,這兩個函數(shù)無法進(jìn)行有效的中斷。所以除了多開線程另起爐灶,沒有好的辦法利用CPU。

NIO的讀寫函數(shù)可以立刻返回,這就給了我們不開線程利用CPU的**好機(jī)會:如果一個連接不能讀寫(socket.read()返回0或者socket.write()返回0),我們可以把這件事記下來,記錄的方式通常是在Selector上注冊標(biāo)記位,然后切換到其它就緒的連接(channel)繼續(xù)進(jìn)行讀寫。

下面具體看下如何利用事件模型單線程處理所有I/O請求:

NIO的主要事件有幾個:讀就緒、寫就緒、有新連接到來。

我們首先需要注冊當(dāng)這幾個事件到來的時候所對應(yīng)的處理器。然后在合適的時機(jī)告訴事件選擇器:我對這個事件感興趣。對于寫操作,就是寫不出去的時候?qū)懯录信d趣;對于讀操作,就是完成連接和系統(tǒng)沒有辦法承載新讀入的數(shù)據(jù)的時;對于accept,一般是服務(wù)器剛啟動的時候;而對于connect,一般是connect失敗需要重連或者直接異步調(diào)用connect的時候。

其次,用一個死循環(huán)選擇就緒的事件,會執(zhí)行系統(tǒng)調(diào)用(Linux 2.6之前是select、poll,2.6之后是epoll,Windows是IOCP),還會阻塞的等待新事件的到來。新事件到來的時候,會在selector上注冊標(biāo)記位,標(biāo)示可讀、可寫或者有連接到來。

注意,select是阻塞的,無論是**操作系統(tǒng)的通知(epoll)還是不停的輪詢(select,poll),這個函數(shù)是阻塞的。所以你可以放心大膽地在一個while(true)里面調(diào)用這個函數(shù)而不用擔(dān)心CPU空轉(zhuǎn)。

所以我們的程序大概的模樣是:

 interface ChannelHandler{
      void channelReadable(Channel channel);
      void channelWritable(Channel channel);
   }
   class Channel{
     Socket socket;
     Event event;//讀,寫或者連接
   }
   //IO線程主循環(huán):
   class IoThread extends Thread{
   public void run(){
   Channel channel;
   while(channel=Selector.select()){//選擇就緒的事件和對應(yīng)的連接
      if(channel.event==accept){
         registerNewChannelHandler(channel);//如果是新連接,則注冊一個新的讀寫處理器
      }
      if(channel.event==write){
         getChannelHandler(channel).channelWritable(channel);//如果可以寫,則執(zhí)行寫事件
      }
      if(channel.event==read){
          getChannelHandler(channel).channelReadable(channel);//如果可以讀,則執(zhí)行讀事件
      }
    }
   }
   Map<Channel,ChannelHandler> handlerMap;//所有channel的對應(yīng)事件處理器
  }

這個程序很簡短,也是**簡單的Reactor模式:注冊所有感興趣的事件處理器,單線程輪詢選擇就緒事件,執(zhí)行事件處理器。

優(yōu)化線程模型

由上面的示例我們大概可以總結(jié)出NIO是怎么解決掉線程的瓶頸并處理海量連接的:

NIO由原來的阻塞讀寫(占用線程)變成了單線程輪詢事件,找到可以進(jìn)行讀寫的網(wǎng)絡(luò)描述符進(jìn)行讀寫。除了事件的輪詢是阻塞的(沒有可干的事情必須要阻塞),剩余的I/O操作都是純CPU操作,沒有必要開啟多線程。

并且由于線程的節(jié)約,連接數(shù)大的時候因?yàn)榫€程切換帶來的問題也隨之解決,進(jìn)而為處理海量連接提供了可能。

單線程處理I/O的效率確實(shí)非常高,沒有線程切換,只是拼命的讀、寫、選擇事件。但現(xiàn)在的服務(wù)器,一般都是多核處理器,如果能夠利用多核心進(jìn)行I/O,無疑對效率會有更大的提高。

仔細(xì)分析一下我們需要的線程,其實(shí)主要包括以下幾種:

事件分發(fā)器,單線程選擇就緒的事件。I/O處理器,包括connect、read、write等,這種純CPU操作,一般開啟CPU核心個線程就可以。業(yè)務(wù)線程,在處理完I/O后,業(yè)務(wù)一般還會有自己的業(yè)務(wù)邏輯,有的還會有其他的阻塞I/O,如DB操作,RPC等。只要有阻塞,就需要單獨(dú)的線程。

Java的Selector對于Linux系統(tǒng)來說,有一個致命限制:同一個channel的select不能被并發(fā)的調(diào)用。因此,如果有多個I/O線程,必須保證:一個socket只能屬于一個IoThread,而一個IoThread可以管理多個socket。

另外連接的處理和讀寫的處理通??梢赃x擇分開,這樣對于海量連接的注冊和讀寫就可以分發(fā)。雖然read()和write()是比較高效無阻塞的函數(shù),但畢竟會占用CPU,如果面對更高的并發(fā)則無能為力。


NIO在客戶端的魔力

**上面的分析,可以看出NIO在服務(wù)端對于解放線程,優(yōu)化I/O和處理海量連接方面,確實(shí)有自己的用武之地。那么在客戶端上,NIO又有什么使用場景呢?

常見的客戶端BIO 連接池模型,可以建立n個連接,然后當(dāng)某一個連接被I/O占用的時候,可以使用其他連接來提高性能。

但多線程的模型面臨和服務(wù)端相同的問題:如果指望增加連接數(shù)來提高性能,則連接數(shù)又受制于線程數(shù)、線程很貴、無法建立很多線程,則性能遇到瓶頸。

每連接順序請求的Redis

對于Redis來說,由于服務(wù)端是全局串行的,能夠保證同一連接的所有請求與返回順序一致。這樣可以使用單線程+隊(duì)列,把請求數(shù)據(jù)緩沖。然后pipeline發(fā)送,返回future,然后channel可讀時,直接在隊(duì)列中把future取回來,done()就可以了。

偽代碼如下:

class RedisClient Implements ChannelHandler{
 private BlockingQueue CmdQueue;
 private EventLoop eventLoop;
 private Channel channel;
 class Cmd{
  String cmd;
  Future result;
 }
 public Future get(String key){
   Cmd cmd= new Cmd(key);
   queue.offer(cmd);
   eventLoop.submit(new Runnable(){
        List list = new ArrayList();
        queue.drainTo(list);
        if(channel.isWritable()){
         channel.writeAndFlush(list);
        }
   });
}
 public void ChannelReadFinish(Channel channel,Buffer Buffer){
    List result = handleBuffer();//處理數(shù)據(jù)
    //從cmdQueue取出future,并設(shè)值,future.done();
}
 public void ChannelWritable(Channel channel){
   channel.flush();
}
}

這樣做,能夠充分的利用pipeline來提高I/O能力,同時獲取異步處理能力。

多連接短連接的HttpClient

類似于競對抓取的項(xiàng)目,往往需要建立無數(shù)的HTTP短連接,然后抓取,然后銷毀,當(dāng)需要單機(jī)抓取上千網(wǎng)站線程數(shù)又受制的時候,怎么保證性能呢?

何不嘗試NIO,單線程進(jìn)行連接、寫、讀操作?如果連接、讀、寫操作系統(tǒng)沒有能力處理,簡單的注冊一個事件,等待下次循環(huán)就好了。

如何存儲不同的請求/響應(yīng)呢?由于http是無狀態(tài)沒有版本的協(xié)議,又沒有辦法使用隊(duì)列,好像辦法不多。比較笨的辦法是對于不同的socket,直接存儲socket的引用作為map的key。

常見的RPC框架,如Thrift,Dubbo

這種框架內(nèi)部一般維護(hù)了請求的協(xié)議和請求號,可以維護(hù)一個以請求號為key,結(jié)果的result為future的map,結(jié)合NIO 長連接,獲取非常不錯的性能。

NIO高級主題

Proactor與Reactor

一般情況下,I/O 復(fù)用機(jī)制需要事件分發(fā)器(event dispatcher)。 事件分發(fā)器的作用,即將那些讀寫事件源分發(fā)給各讀寫事件的處理者,就像送快遞的在樓下喊: 誰誰誰的快遞到了, 快來拿吧!開發(fā)人員在開始的時候需要在分發(fā)器那里注冊感興趣的事件,并提供相應(yīng)的處理者(event handler),或者是回調(diào)函數(shù);事件分發(fā)器在適當(dāng)?shù)臅r候,會將請求的事件分發(fā)給這些handler或者回調(diào)函數(shù)。

涉及到事件分發(fā)器的兩種模式稱為:Reactor和Proactor。 Reactor模式是基于同步I/O的,而Proactor模式是和異步I/O相關(guān)的。在Reactor模式中,事件分發(fā)器等待某個事件或者可應(yīng)用或個操作的狀態(tài)發(fā)生(比如文件描述符可讀寫,或者是socket可讀寫),事件分發(fā)器就把這個事件傳給事先注冊的事件處理函數(shù)或者回調(diào)函數(shù),由后者來做實(shí)際的讀寫操作。

而在Proactor模式中,事件處理者(或者代由事件分發(fā)器發(fā)起)直接發(fā)起一個異步讀寫操作(相當(dāng)于請求),而實(shí)際的工作是由操作系統(tǒng)來完成的。發(fā)起時,需要提供的參數(shù)包括用于存放讀到數(shù)據(jù)的緩存區(qū)、讀的數(shù)據(jù)大小或用于存放外發(fā)數(shù)據(jù)的緩存區(qū),以及這個請求完后的回調(diào)函數(shù)等信息。事件分發(fā)器得知了這個請求,它默默等待這個請求的完成,然后轉(zhuǎn)發(fā)完成事件給相應(yīng)的事件處理者或者回調(diào)。舉例來說,在Windows上事件處理者投遞了一個異步IO操作(稱為overlapped技術(shù)),事件分發(fā)器等IO Complete事件完成。這種異步模式的典型實(shí)現(xiàn)是基于操作系統(tǒng)底層異步API的,所以我們可稱之為“系統(tǒng)級別”的或者“真正意義上”的異步,因?yàn)榫唧w的讀寫是由操作系統(tǒng)代勞的。

舉個例子,將有助于理解Reactor與Proactor二者的差異,以讀操作為例(寫操作類似)。

在Reactor中實(shí)現(xiàn)讀

注冊讀就緒事件和相應(yīng)的事件處理器。事件分發(fā)器等待事件。事件到來,激活分發(fā)器,分發(fā)器調(diào)用事件對應(yīng)的處理器。事件處理器完成實(shí)際的讀操作,處理讀到的數(shù)據(jù),注冊新的事件,然后返還控制權(quán)。

在Proactor中實(shí)現(xiàn)讀:

處理器發(fā)起異步讀操作(注意:操作系統(tǒng)必須支持異步IO)。在這種情況下,處理器無視IO就緒事件,它關(guān)注的是完成事件。事件分發(fā)器等待操作完成事件。在分發(fā)器等待過程中,操作系統(tǒng)利用并行的內(nèi)核線程執(zhí)行實(shí)際的讀操作,并將結(jié)果數(shù)據(jù)存入用戶自定義緩沖區(qū),**后通知事件分發(fā)器讀操作完成。事件分發(fā)器呼喚處理器。事件處理器處理用戶自定義緩沖區(qū)中的數(shù)據(jù),然后啟動一個新的異步操作,并將控制權(quán)返回事件分發(fā)器。

可以看出,兩個模式的相同點(diǎn),都是對某個I/O事件的事件通知(即告訴某個模塊,這個I/O操作可以進(jìn)行或已經(jīng)完成)。在結(jié)構(gòu)上,兩者也有相同點(diǎn):事件分發(fā)器負(fù)責(zé)提交IO操作(異步)、查詢設(shè)備是否可操作(同步),然后當(dāng)條件滿足時,就回調(diào)handler;不同點(diǎn)在于,異步情況下(Proactor),當(dāng)回調(diào)handler時,表示I/O操作已經(jīng)完成;同步情況下(Reactor),回調(diào)handler時,表示I/O設(shè)備可以進(jìn)行某個操作(can read 或 can write)。

下面,我們將嘗試應(yīng)對為Proactor和Reactor模式建立可移植框架的挑戰(zhàn)。在改進(jìn)方案中,我們將Reactor原來位于事件處理器內(nèi)的Read/Write操作移至分發(fā)器(不妨將這個思路稱為“模擬異步”),以此尋求將Reactor多路同步I/O轉(zhuǎn)化為模擬異步I/O。以讀操作為例子,改進(jìn)過程如下:

注冊讀就緒事件和相應(yīng)的事件處理器。并為分發(fā)器提供數(shù)據(jù)緩沖區(qū)地址,需要讀取數(shù)據(jù)量等信息。分發(fā)器等待事件(如在select()上等待)。事件到來,激活分發(fā)器。分發(fā)器執(zhí)行一個非阻塞讀操作(它有完成這個操作所需的全部信息),**后調(diào)用對應(yīng)處理器。事件處理器處理用戶自定義緩沖區(qū)的數(shù)據(jù),注冊新的事件(當(dāng)然同樣要給出數(shù)據(jù)緩沖區(qū)地址,需要讀取的數(shù)據(jù)量等信息),**后將控制權(quán)返還分發(fā)器。 如我們所見,**對多路I/O模式功能結(jié)構(gòu)的改造,可將Reactor轉(zhuǎn)化為Proactor模式。改造前后,模型實(shí)際完成的工作量沒有增加,只不過參與者間對工作職責(zé)稍加調(diào)換。沒有工作量的改變,自然不會造成性能的削弱。對如下各步驟的比較,可以證明工作量的恒定:

標(biāo)準(zhǔn)/典型的Reactor:

步驟1:等待事件到來(Reactor負(fù)責(zé))。步驟2:將讀就緒事件分發(fā)給用戶定義的處理器(Reactor負(fù)責(zé))。步驟3:讀數(shù)據(jù)(用戶處理器負(fù)責(zé))。步驟4:處理數(shù)據(jù)(用戶處理器負(fù)責(zé))。

改進(jìn)實(shí)現(xiàn)的模擬Proactor:

步驟1:等待事件到來(Proactor負(fù)責(zé))。步驟2:得到讀就緒事件,執(zhí)行讀數(shù)據(jù)(現(xiàn)在由Proactor負(fù)責(zé))。步驟3:將讀完成事件分發(fā)給用戶處理器(Proactor負(fù)責(zé))。

步驟4:處理數(shù)據(jù)(用戶處理器負(fù)責(zé))。

對于不提供異步I/O API的操作系統(tǒng)來說,這種辦法可以隱藏Socket API的交互細(xì)節(jié),從而對外暴露一個完整的異步接口。借此,我們就可以進(jìn)一步構(gòu)建完全可移植的,平臺無關(guān)的,有通用對外接口的解決方案。

代碼示例如下:

interface ChannelHandler{
      void channelReadComplate(Channel channel,byte[] data);
      void channelWritable(Channel channel);
   }
   class Channel{
     Socket socket;
     Event event;//讀,寫或者連接
   }
   //IO線程主循環(huán):
   class IoThread extends Thread{
   public void run(){
   Channel channel;
   while(channel=Selector.select()){//選擇就緒的事件和對應(yīng)的連接
      if(channel.event==accept){
         registerNewChannelHandler(channel);//如果是新連接,則注冊一個新的讀寫處理器
         Selector.interested(read);
      }
      if(channel.event==write){
         getChannelHandler(channel).channelWritable(channel);//如果可以寫,則執(zhí)行寫事件
      }
      if(channel.event==read){
          byte[] data = channel.read();
          if(channel.read()==0)//沒有讀到數(shù)據(jù),表示本次數(shù)據(jù)讀完了
          {
          getChannelHandler(channel).channelReadComplate(channel,data;//處理讀完成事件
          }
          if(過載保護(hù)){
          Selector.interested(read);
          }
      }
     }
    }
   Map<Channel,ChannelHandler> handlerMap;//所有channel的對應(yīng)事件處理器
   }

Selector.wakeup()

主要作用

解除阻塞在Selector.select()/select(long)上的線程,立即返回。

兩次成功的select之間多次調(diào)用wakeup等價于一次調(diào)用。

如果當(dāng)前沒有阻塞在select上,則本次wakeup調(diào)用將作用于下一次select——“記憶”作用。

為什么要喚醒?

注冊了新的channel或者事件。

channel關(guān)閉,取消注冊。

優(yōu)先級更高的事件觸發(fā)(如定時器事件),希望及時處理。

原理

Linux上利用pipe調(diào)用創(chuàng)建一個管道,Windows上則是一個loopback的tcp連接。這是因?yàn)閣in32的管道無法加入select的fd set,將管道或者TCP連接加入select fd set。

wakeup往管道或者連接寫入一個字節(jié),阻塞的select因?yàn)橛蠭/O事件就緒,立即返回。可見,wakeup的調(diào)用開銷不可忽視。

Buffer的選擇

通常情況下,操作系統(tǒng)的一次寫操作分為兩步:

將數(shù)據(jù)從用戶空間拷貝到系統(tǒng)空間。從系統(tǒng)空間往網(wǎng)卡寫。同理,讀操作也分為兩步: ① 將數(shù)據(jù)從網(wǎng)卡拷貝到系統(tǒng)空間; ② 將數(shù)據(jù)從系統(tǒng)空間拷貝到用戶空間。

對于NIO來說,緩存的使用可以使用DirectByteBuffer和HeapByteBuffer。如果使用了DirectByteBuffer,一般來說可以減少一次系統(tǒng)空間到用戶空間的拷貝。但Buffer創(chuàng)建和銷毀的成本更高,更不宜維護(hù),通常會用內(nèi)存池來提高性能。

如果數(shù)據(jù)量比較小的中小應(yīng)用情況下,可以考慮使用heapBuffer;反之可以用directBuffer。

NIO存在的問題

使用NIO != 高性能,當(dāng)連接數(shù)<1000,并發(fā)程度不高或者局域網(wǎng)環(huán)境下NIO并沒有顯著的性能優(yōu)勢。

NIO并沒有完全屏蔽平臺差異,它仍然是基于各個操作系統(tǒng)的I/O系統(tǒng)實(shí)現(xiàn)的,差異仍然存在。使用NIO做網(wǎng)絡(luò)編程構(gòu)建事件驅(qū)動模型并不容易,陷阱重重。

推薦大家使用成熟的NIO框架,如Netty,MINA等。解決了很多NIO的陷阱,并屏蔽了操作系統(tǒng)的差異,有較好的性能和編程模型。

總結(jié)

**后總結(jié)一下到底NIO給我們帶來了些什么:

事件驅(qū)動模型避免多線程單線程處理多任務(wù)非阻塞I/O,I/O讀寫不再阻塞,而是返回0基于block的傳輸,通常比基于流的傳輸更高效更高級的IO函數(shù),zero-copyIO多路復(fù)用大大提高了Java網(wǎng)絡(luò)應(yīng)用的可伸縮性和實(shí)用性

本文拋磚引玉,詮釋了一些NIO的思想和設(shè)計理念以及應(yīng)用場景,這只是從冰山一角。關(guān)于NIO可以談的技術(shù)點(diǎn)其實(shí)還有很多,期待未來有機(jī)會和大家繼續(xù)探討。


相關(guān)推薦:


南京java培訓(xùn)   南京java培訓(xùn)班   南京java培訓(xùn)機(jī)構(gòu)

上一篇:昆山j(luò)ava程序培訓(xùn)學(xué)費(fèi)_昆山JAVA培訓(xùn) 下一篇:蘇州java培訓(xùn)機(jī)構(gòu)_蘇州JAVA培訓(xùn)
蘇州JAVA

免費(fèi)體驗(yàn)課開班倒計時

11: 41: 09

稍后會有專業(yè)老師給您回電,請保持電話暢通

咨詢電話:13013833891

校區(qū)導(dǎo)航

1個校區(qū)

蘇州其然軟件開發(fā)
推薦機(jī)構(gòu) 全國分站 更多課程

今日已有25人申請,本月限額500

申請試聽名額

已有10254人申請免費(fèi)試聽

01電話咨詢 | 13013833891

QQ:1413838287
加盟合作:0755-83654572