按照《Unix網路編程》的劃分,IO模型可以分為:阻塞IO、非阻塞IO、IO複用、訊號驅動IO和非同步IO,按照POSIX標準來劃分只分為兩類:同步IO和非同步IO。如何區分呢?首先一個IO操作其實分成了兩個步驟:發起IO請求和實際的IO操作,同步IO和非同步IO的區別就在於第二個步驟是否阻塞,如果實際的IO讀寫阻塞請求進程,那麼就是同步IO,因此阻塞IO、非阻塞IO、IO服用、訊號驅動IO都是同步IO,如果不阻塞,而是作業系統幫你做完IO操作再將結果返回給你,那麼就是非同步IO。阻塞IO和非阻塞IO的區別在於第一步,發起IO請求是否會被阻塞,如果阻塞直到完成那麼就是傳統的阻塞IO,如果不阻塞,那麼就是非阻塞IO。
Java nio 2.0的主要改進就是引入了非同步IO(包括檔案和網路),這裡主要介紹下非同步網路IO API的使用以及架構的設計,以TCP服務端為例。首先看下為了支援AIO引入的新的類和介面:
java.nio.channels.AsynchronousChannel
標記一個channel支援非同步IO操作。
java.nio.channels.AsynchronousServerSocketChannel
ServerSocket的aio版本,建立TCP服務端,綁定地址,監聽連接埠等。
java.nio.channels.AsynchronousSocketChannel
面向流的非同步socket channel,表示一個串連。
java.nio.channels.AsynchronousChannelGroup
非同步channel的分組管理,目的是為了資源共用。一個AsynchronousChannelGroup綁定一個線程池,這個線程池執行兩個任務:處理IO事件和派發CompletionHandler。AsynchronousServerSocketChannel建立的時候可以傳入一個 AsynchronousChannelGroup,那麼通過AsynchronousServerSocketChannel建立的 AsynchronousSocketChannel將同屬於一個組,共用資源。
java.nio.channels.CompletionHandler
非同步IO操作結果的回調介面,用於定義在IO操作完成後所作的回調工作。AIO的API允許兩種方式來處理非同步作業的結果:返回的Future模式或者註冊CompletionHandler,我更推薦用CompletionHandler的方式,這些handler的調用是由 AsynchronousChannelGroup的線程池派發的。顯然,線程池的大小是效能的關鍵因素。AsynchronousChannelGroup允許綁定不同的線程池,通過三個靜態方法來建立:
public static AsynchronousChannelGroup withFixedThreadPool(int nThreads,
ThreadFactory threadFactory)
throws IOException
public static AsynchronousChannelGroup withCachedThreadPool(ExecutorService executor,
int initialSize)
public static AsynchronousChannelGroup withThreadPool(ExecutorService executor)
throws IOException
需要根據具體應用相應調整,從架構角度出發,需要暴露這樣的配置選項給使用者。
在介紹完了aio引入的TCP的主要介面和類之後,我們來設想下一個aio架構應該怎麼設計。參考非阻塞nio架構的設計,一般都是採用Reactor模式,Reacot負責事件的註冊、select、事件的派發;相應地,非同步IO有個Proactor模式,Proactor負責 CompletionHandler的派發,查看一個典型的IO寫操作的流程來看兩者的區別:
Reactor: send(msg) -> 訊息佇列是否為空白,如果為空白 -> 向Reactor註冊OP_WRITE,然後返回 -> Reactor select -> 觸發Writable,通知使用者線程去處理 ->先登出Writable(很多人遇到的cpu 100%的問題就在於沒有登出),處理Writeable,如果沒有完全寫入,繼續註冊OP_WRITE。注意到,寫入的工作還是使用者線程在處理。
Proactor: send(msg) -> 訊息佇列是否為空白,如果為空白,發起read非同步呼叫,並註冊CompletionHandler,然後返回。 -> 作業系統負責將你的訊息寫入,並返回結果(寫入的位元組數)給Proactor -> Proactor派發CompletionHandler。可見,寫入的工作是作業系統在處理,無需使用者線程參與。事實上在aio的API 中,AsynchronousChannelGroup就扮演了Proactor的角色。
CompletionHandler有三個方法,分別對應於處理成功、失敗、被取消(通過返回的Future)情況下的回調處理:
public interface CompletionHandler<V,A> {
void completed(V result, A attachment);
void failed(Throwable exc, A attachment);
void cancelled(A attachment);
}