同類文章:java
http://weixiaolu.iteye.com/blog/1479656程序員
由於前段時間的項目須要寫一些高性能服務器,結果寫出來的結果是五花八門,咱們要求使用NIO編寫異步服務器,可是居然有人把NIO硬生生地寫成同步的,還寫成了一個單道批處理,線程調度、通訊、同步操做,尤如天馬行空,看不出一點架構,典型的麪條代碼,極度暈倒,不得不下定決心,將IO部分與線程調度部分隔離出來。
爲此,狠下心來,仔細研究了一下nio機制和dl的util.concurrent包。
1、NIO的出現
NIO是JDK1.4裏面纔出現的東東,他給你們帶來的最大好處是異步socket。其它file,pipe暫時就很少談了。
在JDK1.4出現以前,若是你須要編寫一個Java服務器,爲了實現異步操做,你必須爲每一個鏈接請求生成一個Java線程,當鏈接請求不少時,線程的調度,上下文切換,所付出的代價是很是昂貴,並且因爲Java是跨平臺的,各個平臺對線程的支持並不相同,性能也不相同,所以傳統的Java服務器編程架構是低效的且代價貴,dl大俠寫了個util.concurrent包後,總算是減輕了線程調度給java程序員帶來的痛苦,可是相比之與C、C++寫出來的服務器,java服務器在性能要求很高的狀況下,基本上沒有什麼競爭力,甚至是入圍的權利的都沒有。
2、異步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也就好寫了,也不會搞得思路混亂了。
3、 異步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年代啊:(
若是出現這樣的須要等待的狀況,應當另起一個線程(推薦使用線程池)去完成這個「長」時間的任務,或者將其它交給一個消息隊列,經過發消息的方式將給別人去完成也行,客戶端能等,你服務器怎麼也能等呢?寫出這樣的代碼,基本上一個服務器也就廢了。
這blog我發在我的的心情隨筆中,發點牢騷,實在是以爲對於有些人的思路以爲有些難以想象,不吐不快。服務器