Java的NIO以及線程並發

來源:互聯網
上載者:User
一、NIO的出現
        NIO是JDK1.4裡面才出現的東東,他給大家帶來的最大好處是非同步socket。其它file,pipe暫時就不多談了。
        在JDK1.4出現之前,如果你需要編寫一個Java伺服器,為了實現非同步作業,你必須為每個串連請求產生一個Java線程,當串連請求很多時,線程的調度,環境切換,所付出的代價是非常昂貴,而且由於Java是跨平台的,各個平台對線程的支援並不相同,效能也不相同,因此傳統的Java伺服器編程架構是低效的且代價貴,dl大俠寫了個util.concurrent包後,總算是減輕了線程調度給java程式員帶來的痛苦,但是相比之與C、C++寫出來的伺服器,java伺服器在效能要求很高的情況下,基本上沒有什麼競爭力,甚至是入圍的權利的都沒有。

二、非同步socket的實現
       NIO出現後,好像讓java的程式員有了楊眉吐氣的機會,怎麼個吐氣法,當時大家是個什麼感受,俺是不知道,因為當時俺不搞java,對java的認識有限。
       NIO是一個基於事件的IO架構,最基本的思想就是:有事件我通知你,你再去做你的事情,沒事件時你大可以節約大把時間去做其它任何事情。而且NIO的主線程only one,不像傳統的模型,需要N個線程去,也減輕了JVM的工作量,使得JVM處理任務時顯得更加高效。
       剛開始接觸NIO時,被N層的Channel架構、網上鋪天蓋地的好評給鎮住了,想想也應當是個很成熟的產品了,網上資料這麼多,抄一抄Jetty、Tomcat以及其它一些牛B的原始碼,基本上就能搞定了,此時沒有想到大家受同步的影響這麼深,也沒有想到連最基本的非同步概念都沒有搞清楚就去寫代碼,搞出一堆的問題來(這是後話,後面再說)。
       現在研究了NIO以後,發現NIO實際上在Java中做的工作是很簡單,就是將事件進行收集和分發,我們結合一個經典的調用例子來說明這個問題,我就不從NIO的基本使用說起了,大家可以查其它的資料,網上一大把。
        當Channel註冊至Selector以後,我們的最經典的調用方法,是這樣子的。
        

 1while(somecondition)
 2{
 3        int n = selector.select(TIMEOUT);
 4        if(n == 0) continue;
 5        for (Iterator iter = selector.selectedKeys().iterator(); iter.hasNext();)
 6        {
 7        if (key.isAcceptable())
 8            doAcceptable(key);
 9        if (key.isConnectable())
10            doConnectable(key);
11        if (key.isValid() && key.isReadable())
12            doReadable(key);
13        if (key.isValid() && key.isWritable())
14            doWritable(key);
15      iter.remove();
16    }
17}

            這隻是個小例子啊,什麼異常我就懶得抓了。
            nio中取得事件通知,就是在selector的select事件中完成的,在selector事件時有一個線程,這個線程具體的處理簡單點說就是:向作業系統詢問,selector中註冊的Channel&&SelectionKey的偶對各種事件是否有發生,如果有則添加到selector的selectedKeys屬性Set中去,並返回本次有多少個感興趣的事情發生。程式員發現這個值>0,表示有事件發生,馬上迭代selectedKeys中的SelectionKey,根據Key中的表示的事件,來做相應的處理。
            實際上,這段說明表明了非同步socket的核心,即非同步socket不過是將多個socket的調度(或者還有他們的線程調度)全部交給作業系統自己去完成,非同步核心Selector,不過是將這些調度收集、分發而已。因為作業系統的socket、線程調度再咋D也比你JVM中要強,效率也高。
            而且就算jvm做的和作業系統一樣好,效能一樣高(當然這是不現實的),使用非同步socket你至少也節約了一半的系統消耗,想想假定作業系統本身也是使用線程來維護N個socket串連,在傳統的java編程中,你還必須為這些socket還多起一個java線程,那至少是2N個線程,現在只需要N+1。在高並發的情況下,你自己去想吧。
        懂了這個道理,非同步socket也就好寫了,也不會搞得思路混亂了。

三、 非同步Socket中應當注意的事情
    3.1 讀
          非同步socket最基本的理念就是事件通知,前面也說了,有事件通知你了,你才該做你應當做的事情。在非同步socket中當註冊了一個OP_READ事件後,你就等著Selector通知你吧,如果沒有通知你,你在家睡大覺都行。
         在這裡,我們有人出現的錯誤就是受同步的影響,自己去主動讀,而且還搞出了多線程,如果仔細考慮一下,就不會出現這個問題了。同步socket中,調用read方法讀取IO中的資料時,通常情況下如果沒有資料read方法會阻塞,且是同步的,所以當多個線程同時訪問時,read方法是安全執行緒的。
         而在非同步下就不同,非同步是不會阻塞的,有什麼就返回什麼,你主動去讀,只要有資料,你就可以拿走,在多線程的情況下,也許你是想讓第一個線程讀取,but此時來資料時正好是線程2讀到了,那線程2就高高興興的拿去,而線程1還在苦苦等待,這樣導致資料混亂不說,如果後面再也不來資料了,線程1就是死迴圈啦。

        2. 寫
        在非同步socket中,寫是唯一一個主動點的操作,但是也不能直接去寫Channel,而是應當先把自身註冊為OP_WRITABLE,這時Selector就會發現你的存在,並把給發一個write事件,你這時後就可以寫了,不過這時候有個小小的技巧,就是你執行寫操作之前,請取消掉你的寫註冊,否則你的cpu肯定是100%。

        3. 等待
        在傳統的伺服器編程中,由於對於每個請求都是產生的一個線程,因此你在你每個請求線程中wait也好,sleep也好,不會影響別人。但是非同步不同,他的主線程只有一個,基本上每個處理都是線性,也就是說處理完第一個,然後才能處理第二個,因此nio是一個極好的處理短串連的架構。
        我們現在出現的問題是,有人受同步的影響,沒有搞清非同步是如何處理,竟然在方法處理中用上sleep,而且一等還是3秒,這意味著什麼,3秒才能處理一個請求,My god,我要一個3秒才能處理一個請求的伺服器幹嘛啊,還是60年代啊:(
        如果出現這樣的需要等待的情況,應當另起一個線程(推薦使用線程池)去完成這個“長”時間的任務,或者將其它交給一個訊息佇列,通過發訊息的方式將給別人去完成也行,用戶端能等,你伺服器怎麼也能等呢?寫出這樣的代碼,基本上一個伺服器也就廢了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.