天天看點

Java NIO類庫Selector機制解析(下) 五、  迷惑不解 : 為什麼要自己消耗資源? 六、  它山之石 : 從Apache的Mina架構了解Selector 七、  真相大白 : 可愛的Java你太不容易了 八、  後記

令人不解的是為什麼我們的Java的New I/O要設計成這個樣子?如果說老的I/O不能多路複用,如下圖所示,要開N多的線程去挨個偵聽每一個Channel (檔案描述符) ,如果這樣做很費資源,且效率不高的話。那為什麼在新的I/O機制依然需要自己連接配接自己,而且,還是重複連接配接,消耗雙倍的資源?

通過WEB搜尋引擎沒有找到為什麼。隻看到N多的人在報BUG,但SUN卻沒有任何解釋。

下面一個圖展示了,老的IO和新IO的在網絡程式設計方面的差别。看起來NIO的确很好很強大。但似乎比起C/C++來說,Java的這種實作會有一些不必要的開銷。

上面的調查沒過多長時間,正好同學趙锟的一個同僚也在開發網絡程式,這位仁兄使用了Apache的Mina架構。當我們把Mina架構的源碼研讀了一下後。發現在Mina中有這麼一個機制:

1)Mina架構會建立一個Work對象的線程。

2)Work對象的線程的run()方法會從一個隊列中拿出一堆Channel,然後使用Selector.select()方法來偵聽是否有資料可以讀/寫。

3)最關鍵的是,在select的時候,如果隊列有新的Channel加入,那麼,Selector.select()會被喚醒,然後重新select最新的Channel集合。

4)要喚醒select方法,隻需要調用Selector的wakeup()方法。

對于熟悉于系統調用的C/C++程式員來說,一個阻塞在select上的線程有以下三種方式可以被喚醒:

1)  有資料可讀/寫,或出現異常。

2)  阻塞時間到,即time out。

3)  收到一個non-block的信号。可由kill或pthread_kill發出。

是以,Selector.wakeup()要喚醒阻塞的select,那麼也隻能通過這三種方法,其中:

1)第二種方法可以排除,因為select一旦阻塞,應無法修改其time out時間。

2)而第三種看來隻能在Linux上實作,Windows上沒有這種信号通知的機制。

是以,看來隻有第一種方法了。再回想到為什麼每個Selector.open(),在Windows會建立一對自己和自己的loopback的TCP連接配接;在Linux上會開一對pipe(pipe在Linux下一般都是成對打開),估計我們能夠猜得出來——那就是如果想要喚醒select,隻需要朝着自己的這個loopback連接配接發點資料過去,于是,就可以喚醒阻塞在select上的線程了。

使用Linux下的strace指令,我們可以友善地證明這一點。參看下圖。圖中,請注意下面幾點:

1)  26654是主線程,之前我輸出notify the select字元串是為了做一個标記,而不至于迷失在大量的strace log中。

2)  26662是偵聽線程,也就是select阻塞的線程。

3)  圖中選中的兩行。26654的write正是wakeup()方法的系統調用,而緊接着的就是26662的epoll_wait的傳回。

從上圖可見,這和我們之前的猜想正好一樣。可見,JDK的Selector自己和自己建的那些TCP連接配接或是pipe,正是用來實作Selector的notify和wakeup的功能的。

這兩個方法完全是來模仿Linux中的的kill和pthread_kill給阻塞在select上的線程發信号的。但因為發信号這個東西并不是一個跨平台的标準(pthread_kill這個系統調用也不是所有Unix/Linux都支援的),而pipe是所有的Unix/Linux所支援的,但Windows又不支援,是以,Windows用了TCP連接配接來實作這個事。

關于Windows,我一直在想,Windows的防火牆的設定是不是會讓Java的類似的程式執行異常呢?呵呵。如果不知道Java的SDK有這樣的機制,誰知道會有多少個程式為此引起的問題度過多少個不眠之夜,尤其是Java程式員。

文章到這裡是可以結束了,但關于Java NIO的Selector引出來的其它話題還有許多,比如關于GNU 的Java編譯器又是如何,它是否會像Sun的Java解釋器如此做傻事?我在這裡先賣一個關子,關于GNU的Java編譯器,我會在另外一篇文章中講述,近期釋出,敬請期待。

關于本文中所使用的實驗平台如下:

·        Windows:Windows XP + SP2, Sun J2SE (build 1.7.0-ea-b23)

·        Linux:Ubuntu 7.10 + Linux Kernel 2.6.22-14-generic, J2SE (build 1.6.0_03-b05)

本文主要的調查工作由我的大學同學趙锟完成,我幫其驗證調查成果及猜想。在此也向大家介紹我的大學同學趙锟,他也是一個技術高手,在軟體開發方面,特别是Unix/Linux C/C++方面有着相當的功底,相信自此以後,會有很多文章會由我和他一同釋出。

本文轉自 haoel 51CTO部落格,原文連結:http://blog.51cto.com/haoel/124578,如需轉載請自行聯系原作者