很早就聽說tomcat6使用nio了,這幾天突然想到一個問題,使用nio代替傳統的bio,ThreadLocal豈不是會存在沖突?
  
  
  
  首先,何謂nio?
  
  如果讀者有socket的編程基礎,應該會接觸過堵塞socket和非堵塞socket,堵塞socket就是在accept、read、write等IO操作的的時候,如果沒有可用符合條件的資源,不馬上返回,一直等待直到有資源為止。而非堵塞socket則是在執行select的時候,當沒有資源的時候堵塞,當有符合資源的時候,返回一個信號,然后程序就可以執行accept、read、write等操作,這個時候,這些操作是馬上完成,并且馬上返回。而windows的winsock則有所不同,可以綁定到一個EventHandle里,也可以綁定到一個HWND里,當有資源到達時,發出事件,這時執行的io操作也是馬上完成、馬上返回的。一般來說,如果使用堵塞socket,通常我們時開一個線程accept socket,當有socket鏈接的時候,開一個單獨的線程處理這個socket;如果使用非堵塞socket,通常是只有一個線程,一開始是select狀態,當有信號的時候馬上處理,然后繼續select狀態。
  
  按照大多數人的說法,堵塞socket比非堵塞socket的性能要好。不過也有小部分人并不是這樣認為的,例如Indy項目(Delphi一個比較出色的網絡包),它就是使用多線程+堵塞socket模式的。另外,堵塞socket比非堵塞socket容易理解,符合一般人的思維,編程相對比較容易。
  
  nio其實也是類似上面的情況。在JDK1.4,sun公司大范圍提升Java的性能,其中NIO就是其中一項。Java的IO操作集中在java.io這個包中,是基于流的阻塞API(即BIO,Block IO)。對于大多數應用來說,這樣的API使用很方便,然而,一些對性能要求較高的應用,尤其是服務端應用,往往需要一個更為有效的方式來處理IO。從JDK 1.4起,NIO API作為一個基于緩沖區,并能提供非阻塞O操作的API(即NIO,non-blocking IO)被引入。
  
  BIO與NIO一個比較重要的不同,是我們使用BIO的時候往往會引入多線程,每個連接一個單獨的線程;而NIO則是使用單線程或者只使用少量的多線程,每個連接共用一個線程。
  
  
  
  這個時候,問題就出來了:我們非常多的java應用是使用ThreadLocal的,例如JSF的FaceContext、Hibernate的session管理、Struts2的Context的管理等等,幾乎所有框架都或多或少地應用ThreadLocal。如果存在沖突,那豈不驚天動地?
  
  后來終于在Tomcat6的文檔(http://tomcat.apache.org/tomcat-6.0-doc/aio.html)找到答案。根據上面說明,應該Tomcat6應用nio只是用在處理發送、接收信息的時候用到,也就是說,tomcat6還是傳統的多線程Servlet,我畫了下面兩個圖來列出區別:
  
  
  
  tomcat5:客戶端連接到達 -> 傳統的SeverSocket.accept接收連接 -> 從線程池取出一個線程 -> 在該線程讀取文本并且解析HTTP協議 -> 在該線程生成ServletRequest、ServletResponse,取出請求的Servlet -> 在該線程執行這個Servlet -> 在該線程把ServletResponse的內容發送到客戶端連接 -> 關閉連接。
  
  我以前理解的使用nio后的tomcat6:客戶端連接到達 -> nio接收連接 -> nio使用輪詢方式讀取文本并且解析HTTP協議(單線程) -> 生成ServletRequest、ServletResponse,取出請求的Servlet -> 直接在本線程執行這個Servlet -> 把ServletResponse的內容發送到客戶端連接 -> 關閉連接。
  
  實際的tomcat6:客戶端連接到達 -> nio接收連接 -> nio使用輪詢方式讀取文本并且解析HTTP協議(單線程) -> 生成ServletRequest、ServletResponse,取出請求的Servlet -> 從線程池取出線程,并在該線程執行這個Servlet -> 把ServletResponse的內容發送到客戶端連接 -> 關閉連接。
  
  
  
  從上圖可以看出,BIO與NIO的不同,也導致進入客戶端處理線程的時刻有所不同:tomcat5在接受連接后馬上進入客戶端線程,在客戶端線程里解析HTTP協議,而tomcat6則是解析完HTTP協議后才進入多線程,另外,tomcat6也比5早脫離客戶端線程的環境。
  
  實際的tomcat6與我之前猜想的差別主要集中在如何處理servlet的問題上。實際上即使拋開ThreadLocal的問題,我之前理解tomcat6只使用一個線程處理的想法其實是行不同的。大家都有經驗:servlet是基于BIO的,執行期間會存在堵塞的,例如讀取文件、數據庫操作等等。tomcat6使用了nio,但不可能要求servlet里面要使用nio,而一旦存在堵塞,效率自然會銳降。
  
  
  
  
  所以,最終的結論當然是tomcat6的servlet里面,ThreadLocal照樣可以使用,不存在沖突。