故事的背景:
有一個(gè)動(dòng)物園,動(dòng)物園里面有獅子,老虎,猴子等動(dòng)物,他們每天定期來(lái)吃不同的水果,而這些水果要由動(dòng)物園管理員到貨場(chǎng)領(lǐng)取分配。
IO
我們知道,在使用IO的時(shí)候往往可以在服務(wù)器端使用多線程或者線程池來(lái)處理并發(fā)請(qǐng)求,這也叫偽異步,那么故事開始了:
在貨場(chǎng)里有五個(gè)動(dòng)物園管理員等待水果貨車的到來(lái),他們一直在等待,如果貨車不來(lái)他們也要死等,直到貨車的到來(lái),這時(shí)候貨車來(lái)了,每個(gè)管理員都領(lǐng)了一份水果去尋找動(dòng)物,管理員A來(lái)到獅子旁邊,給獅子一份蘋果,獅子開始吃,中途吃累了,休息了一會(huì)兒繼續(xù)吃,這時(shí)候管理員A就一直等著獅子吃完,再回去拿水果去另一個(gè)動(dòng)物那里。五個(gè)管理員都是這樣做的。
NIO
Java NIO是在jdk1.4開始使用的,它既可以說(shuō)成“新IO”,也可以說(shuō)成非阻塞式I/O。下面是java NIO的工作原理:
由一個(gè)專門的線程來(lái)處理所有的IO事件,并負(fù)責(zé)分發(fā)。
事件驅(qū)動(dòng)機(jī)制:事件到的時(shí)候觸發(fā),而不是同步的去監(jiān)視事件。
線程通訊:線程之間通過(guò)wait,notify等方式通訊。保證每次上下文切換都是有意義的。減少無(wú)謂的線程切換。
那么故事開始了:動(dòng)物園里,有一個(gè)管理員他先去了解動(dòng)物們都喜歡吃什么樣的水果并做了一個(gè)登記,然后去貨場(chǎng)看一看貨車有沒(méi)有到來(lái),如果沒(méi)有來(lái)他就回去繼續(xù)干別的事,就這樣每隔一段時(shí)間就去貨場(chǎng)看一下。這時(shí)候貨車來(lái)了,他叫來(lái)另外五個(gè)管理員并告訴管理員哪些動(dòng)物喜歡吃哪些水果,管理員們分類取不同的水果,然后各自去找動(dòng)物們分發(fā)水果,還是管理員A,來(lái)到獅子旁邊把它喜歡吃的蘋果給它吃,獅子吃的比較慢,這時(shí)候管理員A說(shuō),你先吃著,我給其他動(dòng)物送水果,我一會(huì)兒再來(lái)取水果盤子,這時(shí)候管理員A回去取來(lái)香焦繼續(xù)猴子送去,在猴子吃香焦累了休息的時(shí)候,管理員A來(lái)找獅子拿回水果盤子,再去找猴子拿回水果盤子。
不知道大家在看完這二個(gè)小故事之后有沒(méi)有理解NIO和IO,那么在下次分享的時(shí)候,咱們將正式進(jìn)入NIO原理及源碼的分享,謝謝大家。
概念
- 異步:某個(gè)事情需要10s完成。而我只需要調(diào)用某個(gè)函數(shù)告訴xxx來(lái)幫我做(然后我再干其他的事情)
- 同步:某個(gè)事情需要10s完成,我需要一直等它完成(等10s),再能繼續(xù)后面的工作。
- 阻塞:做某件事情,直到完成,除非超時(shí)
- 非阻塞:嘗試做,如果不能做,就不做(直接返回),如果能做,就做。
前兩者和后兩者不容易區(qū)分,不過(guò)前兩者更多的有涉及到多線程交互(消息)的場(chǎng)景。
舉個(gè)例子
小李喝了想喝水,于是去煮開水。
1、小李把水壺放到爐子上,等待水燒開。(同步阻塞)
小李感覺這樣太費(fèi)時(shí)間。
2、小李把水壺放到爐子上,去客廳看電視,時(shí)不時(shí)去廚房看看水開沒(méi)有。(同步非阻塞)
小李還是覺得自己這樣太累,于是買了把會(huì)響笛的那種水壺。水開之后,能發(fā)出聲音。
3、小李把響水壺放到爐子上,等待水壺發(fā)出聲音。(異步阻塞)
覺得這樣傻等意義不大
5、小李把響水壺放到爐子上,去客廳看電視,水壺響之前不再去看它了,響了再去拿壺。(異步非阻塞)
這樣真好。
深入理解
阻塞就是 recv/read的時(shí)候 socket接收緩沖區(qū)要是有數(shù)據(jù)就讀, 沒(méi)數(shù)據(jù)我就一直睡覺賴著不走,直到有數(shù)據(jù)來(lái)了讀完我才走。send/write的時(shí)候,要是發(fā)送緩沖區(qū)滿了,沒(méi)有空間繼續(xù)發(fā)送了我也一直睡覺賴著不走,直到發(fā)送緩沖區(qū)騰出足夠的空間讓我把數(shù)據(jù)全部塞到發(fā)送緩沖區(qū)里我才走。(當(dāng)然如果你通過(guò)setsockopt設(shè)置了讀寫超時(shí),超時(shí)時(shí)間到了還是會(huì)返回-1和EAGAIN,不再睡覺等待)
非阻塞就是recv/read的時(shí)候,要是接收緩沖區(qū)有數(shù)據(jù)我就讀完,沒(méi)有數(shù)據(jù)我直接帶著返回的-1和EGAIN走人,絕不睡覺等待耽誤時(shí)間。write/send的時(shí)候, 要是發(fā)送緩沖區(qū)有足夠的空間,就立刻把數(shù)據(jù)塞到發(fā)送緩沖區(qū)去,然后走人,如果發(fā)送緩存區(qū)滿了,空間不足,那直接帶著返回的-1和EAGAIN走人。
至于IO多路復(fù)用,首先要理解的是,操作系統(tǒng)為你提供了一個(gè)功能,當(dāng)你的某個(gè)socket接收緩存區(qū)有數(shù)據(jù)可讀,或者發(fā)送緩沖區(qū)有空間可寫的時(shí)候,它可以給你一個(gè)通知。這樣當(dāng)配合非阻塞的socket使用時(shí),只有當(dāng)系統(tǒng)通知我哪個(gè)描述符可讀了,我才去執(zhí)行read操作,可以保證每次read都能讀到有效數(shù)據(jù)而不做純返回-1和EAGAIN的無(wú)用功。寫操作類似。操作系統(tǒng)的這個(gè)功能通過(guò)select/poll/epoll之類的系統(tǒng)調(diào)用函數(shù)來(lái)使用,這些函數(shù)都可以同時(shí)監(jiān)視多個(gè)描述符的讀寫就緒狀況,這樣,多個(gè)描述符的I/O操作都能在一個(gè)線程內(nèi)完成,這就叫I/O多路復(fù)用,這里的“復(fù)用”指的是復(fù)用同一個(gè)線程。
至于事件驅(qū)動(dòng),其實(shí)是I/O多路復(fù)用的一個(gè)另外的稱呼。