java 同步/非同步IO和阻塞/非阻塞IO 關係和概念解析
I/O的模型
首先要宣告的一點一定要把同步/非同步 阻塞/非阻塞 以及I/O這三者的概念區別開來,同步大部分是阻塞
的,非同步大部分是非阻塞的,但是它們之間並沒有必然的因果關係
同步與非同步
兩者產生需要有個前提——是否有多個任務或事件發生,只有滿足了這一前提,才有了同步和非同步的概念
同步:如果有多個任務或者事件要發生,這些任務或者事件必須逐個地進行,一個事件或者任務
的執行會導致整個流程的暫時等待,這些事件沒有辦法併發地執行
非同步:如果有多個任務或者事件發生,這些事件可以併發地執行,一個事件或者任務的執行不會
導致整個流程的暫時等待
再擴充套件一點,所謂同步,就是在發出一個功能呼叫時,在沒有得到結果之前,該呼叫就不返回。按照這個定義,
其實絕大多數函式都是同步呼叫。但是一般而言,我們在說同步、非同步的時候,特指那些需要其他部件協作或
者需要一定時間完成的任務
舉個例子,小明他媽(呼叫方)派小明(被呼叫方)去車站迎接客人,小明一直在車站等到客人到達,把客人
帶回家,交給他媽,這就是同步呼叫。
小明嫌在車站等著無聊,改為每隔五分鐘就出去看一次,立即回來告訴他媽客人到沒到,這就是非同步呼叫
阻塞和非阻塞
這兩個概念的前提條件是某個事件或者任務在執行
阻塞:當某個事件或者任務在執行過程中,它發出一個請求操作,但是由於該請求操作需要的條件
不滿足,那麼就會一直在那等待,直至條件滿足
非阻塞:當某個事件或者任務在執行過程中,它發出一個請求操作,如果該請求操作需要的條件不
滿足,會立即返回一個標誌資訊告知條件不滿足,不會一直在那等待
因此同步/非同步和阻塞/非阻塞沒有必聯的關係,至於為什麼有時候會感覺兩者有聯絡呢?是因為java中為
了使多執行緒同步,會用上鎖的方式鎖住某一共享資源,等待鎖的執行緒就被阻塞了,感覺上為了同步就有了阻
塞。其實不然,因為同步/非同步和阻塞/非阻塞概念產生的前提是不一樣的,前兩者是多工,後者是單任務
執行過程中遇到阻礙
阻塞/非阻塞, 它們是程式在等待訊息(無所謂同步或者非同步)時的狀態
阻塞呼叫是指呼叫結果返回之前,當前執行緒會被掛起。有人也許會把阻塞呼叫和同步呼叫等同起來,實際上他是不同的。對於同步呼叫來說,很多時候當前執行緒還是啟用的,只是從邏輯上當前函式沒有返回而已。
非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函式不會阻塞當前執行緒,而會立刻返回。
還是小明他媽(呼叫方)派小明(被呼叫方)去車站迎接客人,在客人到來之前,小明他媽什麼都不幹,專心等待客人,這就是阻塞呼叫
後來,小明他媽變聰明瞭,在客人到來之前,她可以洗菜、拖地、聽聽歌,客人來了之後再招待客人,這就是非阻塞呼叫
I/O的操作過程
通常來說,IO操作包括:對硬碟的讀寫、對socket的讀寫以及外設的讀寫,並且需要進行使用者空間和核心空
間的區分(使用者空間就是普通的使用者程序,核心空間就是核心程序,只有核心空間才可以直接範圍磁碟等物理
I/O裝置)
使用者空間產生一個讀請求,請求再轉交由核心空間執行
1. 核心檢查讀取的資料是否就緒
2. 如果就緒,核心將資料從核心空間複製到使用者空間(記憶體上拷貝)
阻塞I/O與非阻塞I/O
阻塞I/O:核心在檢查資料未就緒時,會一直等待,直到資料就緒
非阻塞I/O:如果資料沒有就緒,則會返回一個標誌資訊告知使用者執行緒當前要讀的資料沒有就緒
它們的區別在於I/O的第一階段,阻塞是選擇等待,非阻塞是返回一個標誌資訊
那麼非阻塞I/O的優勢在哪裡呢?使用阻塞I/O處理網路連線時,有10000個連線就要開10000個執行緒,無論
有沒有資料到來,處理某一連線的執行緒必須“忠實地阻塞”。而非阻塞I/O就不需要這樣,它可以維護一個1000
個執行緒的執行緒池,當有資料就緒時,啟動一個執行緒去接受資料,當沒有資料時,執行緒不需要等待,直接就可以
回到池中,等待被排程到去接受其它連線。因此非阻塞I/O非常適合連線多但傳輸的資料內容不大的情況,如
果連線少資料多,阻塞I/O更容易程式設計
同步I/O和非同步I/O
事實上,同步IO和非同步IO模型是針對使用者執行緒和核心的互動來說的,即資料是否就緒的訊息傳遞機制
同步IO:當用戶發出IO請求操作之後,如果資料沒有就緒,需要通過使用者執行緒或者核心不斷地去輪詢資料是否
就緒,當資料就緒時,再將資料從核心拷貝到使用者執行緒
非同步IO:只有IO請求操作的發出是由使用者執行緒來進行的,核心自動完成檢查資料是否就緒和將資料拷貝
到使用者空間的過程,然後傳送通知告知使用者執行緒IO操作已經完成。
在《Unix網路程式設計》書中說到:
A synchronous I/O operation causes the requesting process to be blocked until
that I/O operation completes.An asynchronous I/O operation does not cause the requesting process to be blocked.
也許會奇怪在這裡說到同步I/O會阻塞,非同步I/O不會阻塞,那麼是不是意味著同步I/O必然是阻塞I/O呢?
其實不然,同步/非同步I/O阻塞的地方和之前說的阻塞/非阻塞IO阻塞的地方不一樣。同步/非同步I/O阻塞的地方
是使用者或核心需要輪詢資料是否就緒,而阻塞/非阻塞IO是阻塞在資料未就緒的狀態下。
同步阻塞I/O=UNIX 阻塞I/O
java 傳統IO模型(java.io.* 又稱為BIO Blocking-IO) 使用者執行緒發起I/O請求,再轉交給核心,輪詢
核心資料是否就緒(同步I/O),核心通知使用者空間資料未就緒,使用者執行緒選擇等待(阻塞I/O)
同步非阻塞I/O=UNIX 非阻塞I/O
使用者執行緒發起I/O請求,再轉交給核心,並不斷輪詢核心資料是否就緒(同步I/O),核心通知使用者空間資料未
就緒,使用者執行緒直接返回標誌資訊不等待(非阻塞I/O)
while(true){
data = socket.read();
if(data!= error){
處理資料
break;
}
}
同步非阻塞I/O=UNIX 多路複用I/O
java nio模型(java.nio.* jdk1.4/1.5 NoBlocking-IO) 在多路複用IO模型中,會有一個執行緒不斷
去輪詢多個socket的狀態,只有當socket真正有讀寫事件時,才真正呼叫實際的IO讀寫操作,只需要使用
一個執行緒就可以管理多個socket
在Java NIO中,是通過selector.select()去查詢每個通道是否有到達事件,如果沒有資料到達,則跳過
有資料到達,就處理資料。因此它可以歸類為同步非阻塞I/O
其實就是一個執行緒輪詢方式(同步I/O)管理多個非阻塞(非阻塞I/O)的通道(channel),多路複用在這個管理
執行緒可管理多個I/O通道
非同步阻塞I/O=UNIX 訊號驅動I/O
在訊號驅動IO模型中,當用戶執行緒發起一個IO請求操作,會給對應的socket註冊一個訊號函式,然後使用者
執行緒會繼續執行,當核心資料就緒時會發送一個訊號給使用者執行緒(非同步I/O),使用者執行緒接收到訊號之後,便
在訊號函式中呼叫IO讀寫操作來進行實際的IO請求操作(阻塞I/O 因為將資料從核心空間複製到使用者空間時
使用者執行緒會被阻塞)
非同步非阻塞I/O=UNIX 非同步IO
java的 nio.2模型(jdk1.7 AsynchronousChannel)
真正的非同步非阻塞I/O,最理想的I/O模型。當用戶執行緒發起read操作之後,立刻就可以開始去做其它的事,
而另一方面,從核心的角度,當它受到一個asynchronous read之後,它會立刻返回,說明read請求已經
成功發起了,因此不會對使用者執行緒產生任何block。然後,核心會等待資料準備完成,然後將資料拷貝到用
戶執行緒,當這一切都完成之後,核心會給使用者執行緒傳送一個訊號,告訴它read操作完成了。也就說使用者執行緒
完全不需要實際的整個IO操作是如何進行的,只需要先發起一個請求,當接收核心返回的成功訊號時表示IO
操作已經完成,可以直接去使用者空間使用資料了。
在I/O的第一第二階段,使用者執行緒都不需要管,不需要輪詢是否就緒(非同步),也不需要等待就緒後資料拷貝
的時間(非阻塞),當然也不存在如果沒就緒的話執行緒的等待(非阻塞)
總結一下,除了非同步非阻塞I/O=UNIX 非同步IO 其他I/O模型都是阻塞的:
1. 同步阻塞I/O=UNIX 阻塞I/O 同步在使用者執行緒輪詢,阻塞在資料未就緒和就緒後的複製
2. 同步非阻塞I/O=UNIX 非阻塞I/O 同步在使用者執行緒輪詢,資料未就緒時不阻塞,阻塞在就緒後複製
3. 同步非阻塞I/O=UNIX 多路複用I/O 同步在使用者執行緒輪詢,資料未就緒時不阻塞,阻塞在就緒後複製
但與UNIX 非阻塞I/O區別在於,由一個執行緒來維護多個非阻塞通道,屬於輕微加強版
4. 非同步阻塞I/O=UNIX 訊號驅動I/O 核心通知是否就緒,資料未就緒時不阻塞,阻塞在就緒後複製
5. 非同步非阻塞I/O=UNIX 非同步IO 核心通知是否就緒,絕對不阻塞
同步/非同步IO——在於訊息通知
阻塞/非阻塞IO——在於資料未就緒時阻塞麼?資料就緒時,需要使用者執行緒等待資料從核心複製到使用者空間麼?
非同步I/O需要作業系統的支援