IO与NIO
1、阻塞與非阻塞
阻塞與非阻塞是描述進(jìn)程在訪問某個(gè)資源時(shí),數(shù)據(jù)是否準(zhǔn)備就緒的的一種處理方式。當(dāng)數(shù)據(jù)沒有準(zhǔn)備就緒時(shí):
- 阻塞:線程持續(xù)等待資源中數(shù)據(jù)準(zhǔn)備完成,直到返回響應(yīng)結(jié)果。
- 非阻塞:線程直接返回結(jié)果,不會(huì)持續(xù)等待資源準(zhǔn)備數(shù)據(jù)結(jié)束后才響應(yīng)結(jié)果。
2、同步與異步
- 同步與異步是指訪問數(shù)據(jù)的機(jī)制,同步一般指主動(dòng)請(qǐng)求并等待IO操作完成的方式。
- 異步則指主動(dòng)請(qǐng)求數(shù)據(jù)后便可以繼續(xù)處理其它任務(wù),隨后等待IO操作完畢的通知。
老王燒開水:
1、普通水壺煮水,站在旁邊,主動(dòng)的看水開了沒有?同步的阻塞
2、普通水壺煮水,去干點(diǎn)別的事,每過一段時(shí)間去看看水開了沒有,水沒開就走人。 同步非阻塞
3、響水壺煮水,站在旁邊,不會(huì)每過一段時(shí)間主動(dòng)看水開了沒有。如果水開了,水壺自動(dòng)通知他。 異步阻塞
4、響水壺煮水,去干點(diǎn)別的事,如果水開了,水壺自動(dòng)通知他。異步非阻塞
基礎(chǔ)概念
1、傳統(tǒng)BIO模型
傳統(tǒng)BIO是一種同步的阻塞IO,IO在進(jìn)行讀寫時(shí),該線程將被阻塞,線程無法進(jìn)行其它操作。
IO流在讀取時(shí),會(huì)阻塞。直到發(fā)生以下情況:1、有數(shù)據(jù)可以讀取。2、數(shù)據(jù)讀取完成。3、發(fā)生異常
2、偽異步IO模型
以傳統(tǒng)BIO模型為基礎(chǔ),通過線程池的方式維護(hù)所有的IO線程,實(shí)現(xiàn)相對(duì)高效的線程開銷及管理。
3、NIO模型
NIO(JDK1.4)模型是一種同步非阻塞IO,主要有三大核心部分:Channel(通道),Buffer(緩沖區(qū)), Selector(多路復(fù)用器)。傳統(tǒng)IO基于字節(jié)流和字符流進(jìn)行操作,而NIO基于Channel和Buffer(緩沖區(qū))進(jìn)行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫入到通道中。Selector(多路復(fù)用器)用于監(jiān)聽多個(gè)通道的事件(比如:連接打開,數(shù)據(jù)到達(dá))。因此,單個(gè)線程可以監(jiān)聽多個(gè)數(shù)據(jù)通道。
NIO和傳統(tǒng)IO(一下簡(jiǎn)稱IO)之間第一個(gè)最大的區(qū)別是,IO是面向流的,NIO是面向緩沖區(qū)的。 Java IO面向流意味著每次從流中讀一個(gè)或多個(gè)字節(jié),直至讀取所有字節(jié),它們沒有被緩存在任何地方。此外,它不能前后移動(dòng)流中的數(shù)據(jù)。如果需要前后移動(dòng)從流中讀取的數(shù)據(jù),需要先將它緩存到一個(gè)緩沖區(qū)。NIO的緩沖導(dǎo)向方法略有不同。數(shù)據(jù)讀取到一個(gè)它稍后處理的緩沖區(qū),需要時(shí)可在緩沖區(qū)中前后移動(dòng)。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩沖區(qū)中包含所有您需要處理的數(shù)據(jù)。而且,需確保當(dāng)更多的數(shù)據(jù)讀入緩沖區(qū)時(shí),不要覆蓋緩沖區(qū)里尚未處理的數(shù)據(jù)。
IO的各種流是阻塞的。這意味著,當(dāng)一個(gè)線程調(diào)用read() 或 write()時(shí),該線程被阻塞,直到有一些數(shù)據(jù)被讀取,或數(shù)據(jù)完全寫入。該線程在此期間不能再干任何事情了。 NIO的非阻塞模式,使一個(gè)線程從某通道發(fā)送請(qǐng)求讀取數(shù)據(jù),但是它僅能得到目前可用的數(shù)據(jù),如果目前沒有數(shù)據(jù)可用時(shí),就什么都不會(huì)獲取。而不是保持線程阻塞,所以直至數(shù)據(jù)變的可以讀取之前,該線程可以繼續(xù)做其他的事情。 非阻塞寫也是如此。一個(gè)線程請(qǐng)求寫入一些數(shù)據(jù)到某通道,但不需要等待它完全寫入,這個(gè)線程同時(shí)可以去做別的事情。 線程通常將非阻塞IO的空閑時(shí)間用于在其它通道上執(zhí)行IO操作,所以一個(gè)單獨(dú)的線程現(xiàn)在可以管理多個(gè)輸入和輸出通道(channel)。
NIO優(yōu)點(diǎn):
Channel(通道)
傳統(tǒng)IO操作對(duì)read()或write()方法的調(diào)用,可能會(huì)因?yàn)闆]有數(shù)據(jù)可讀/可寫而阻塞,直到有數(shù)據(jù)響應(yīng)。也就是說讀寫數(shù)據(jù)的IO調(diào)用,可能會(huì)無限期的阻塞等待,效率依賴網(wǎng)絡(luò)傳輸?shù)乃俣取W钪匾氖窃谡{(diào)用一個(gè)方法前,無法知道是否會(huì)被阻塞。
NIO的Channel抽象了一個(gè)重要特征就是可以通過配置它的阻塞行為,來實(shí)現(xiàn)非阻塞式的通道。
Channel是一個(gè)雙向通道,與傳統(tǒng)IO操作只允許單向的讀寫不同的是,NIO的Channel允許在一個(gè)通道上進(jìn)行讀和寫的操作。
FileChannel:文件
SocketChannel:
ServerSocketChannel:
DatagramChannel: UDP
Buffer(緩沖區(qū))
Bufer顧名思義,它是一個(gè)緩沖區(qū),實(shí)際上是一個(gè)容器,一個(gè)連續(xù)數(shù)組。Channel提供從文件、網(wǎng)絡(luò)讀取數(shù)據(jù)的渠道,但是讀寫的數(shù)據(jù)都必須經(jīng)過Buffer。
Buffer(緩沖區(qū))
Buffer緩沖區(qū)本質(zhì)上是一塊可以寫入數(shù)據(jù),然后可以從中讀取數(shù)據(jù)的內(nèi)存。這塊內(nèi)存被包裝成NIO Buffer對(duì)象,并提供了一組方法,用來方便的訪問該模塊內(nèi)存。為了理解Buffer的工作原理,需要熟悉它的三個(gè)屬性:capacity、position和limit。
position和limit的含義取決于Buffer處在讀模式還是寫模式。不管Buffer處在什么模式,capacity的含義總是一樣的。見下圖:
capacity、position和limit
- capacity:作為一個(gè)內(nèi)存塊,Buffer有固定的大小值,也叫作“capacity”,只能往其中寫入capacity個(gè)byte、long、char等類型。一旦Buffer滿了,需要將其清空(通過讀數(shù)據(jù)或者清楚數(shù)據(jù))才能繼續(xù)寫數(shù)據(jù)。
- position:當(dāng)你寫數(shù)據(jù)到Buffer中時(shí),position表示當(dāng)前的位置。出事的position值為0,當(dāng)寫入一個(gè)字節(jié)數(shù)據(jù)到Buffer中后,position會(huì)向前移動(dòng)到下一個(gè)可插入數(shù)據(jù)的Buffer單元。position最大可為capacity-1。當(dāng)讀取數(shù)據(jù)時(shí),也是從某個(gè)特定位置讀,講Buffer從寫模式切換到讀模式,position會(huì)被重置為0。當(dāng)從Buffer的position處讀取一個(gè)字節(jié)數(shù)據(jù)后,position向前移動(dòng)到下一個(gè)可讀的位置。
- limit:在寫模式下,Buffer的limit表示你最多能往Buffer里寫多少數(shù)據(jù)。 寫模式下,limit等于Buffer的capacity。當(dāng)切換Buffer到讀模式時(shí), limit表示你最多能讀到多少數(shù)據(jù)。因此,當(dāng)切換Buffer到讀模式時(shí),limit會(huì)被設(shè)置成寫模式下的position值。換句話說,你能讀到之前寫入的所有數(shù)據(jù)(limit被設(shè)置成已寫數(shù)據(jù)的數(shù)量,這個(gè)值在寫模式下就是position)
Buffer的分配:
對(duì)Buffer對(duì)象的操作必須首先進(jìn)行分配,Buffer提供一個(gè)allocate(int capacity)方法分配一個(gè)指定字節(jié)大小的對(duì)象。
向Buffer中寫數(shù)據(jù):寫數(shù)據(jù)到Buffer中有兩種方式:
1.從channel寫到Buffer
2.通過Buffer的put()方法寫到Buffer
buf.put(byte); //將數(shù)據(jù)通過put()方法寫入到buf中- flip()方法:將Buffer從寫模式切換到讀模式,調(diào)用flip()方法會(huì)將position設(shè)置為0,并將limit設(shè)置為之前的position的值。
從Buffer中讀數(shù)據(jù):從Buffer中讀數(shù)據(jù)有兩種方式:
1.從Buffer讀取數(shù)據(jù)到Channel
2.通過Buffer的get()方法讀取數(shù)據(jù)
byte bt = buf.get(); //從buf中讀取一個(gè)byte- rewind()方法:Buffer.rewind()方法將position設(shè)置為0,使得可以重讀Buffer中的所有數(shù)據(jù),limit保持不變。
- clear()與compact()方法:一旦讀完Buffer中的數(shù)據(jù),需要讓Buffer準(zhǔn)備好再次被寫入,可以通過clear()或compact()方法完成。如果調(diào)用的是clear()方法,position將被設(shè)置為0,limit設(shè)置為capacity的值。但是Buffer并未被清空,只是通過這些標(biāo)記告訴我們可以從哪里開始往Buffer中寫入多少數(shù)據(jù)。如果Buffer中還有一些未讀的數(shù)據(jù),調(diào)用clear()方法將被"遺忘 "。compact()方法將所有未讀的數(shù)據(jù)拷貝到Buffer起始處,然后將position設(shè)置到最后一個(gè)未讀元素的后面,limit屬性依然設(shè)置為capacity。可以使得Buffer中的未讀數(shù)據(jù)還可以在后續(xù)中被使用。
- mark()與reset()方法:通過調(diào)用Buffer.mark()方法可以標(biāo)記一個(gè)特定的position,之后可以通過調(diào)用Buffer.reset()恢復(fù)到這個(gè)position上。
Selector(多路復(fù)用器)
Selector與Channel是相互配合使用的,將Channel注冊(cè)在Selector上之后,才可以正確的使用Selector,但此時(shí)Channel必須為非阻塞模式。Selector可以監(jiān)聽Channel的四種狀態(tài)(Connect、Accept、Read、Write),當(dāng)監(jiān)聽到某一Channel的某個(gè)狀態(tài)時(shí),才允許對(duì)Channel進(jìn)行相應(yīng)的操作。
- Connect:某一個(gè)客戶端連接成功后
- Accept:準(zhǔn)備好進(jìn)行連接
- Read:可讀
- Write:可寫
總結(jié)
- 上一篇: Controller层使用@value注
- 下一篇: Mybatis 插入时获取主键的方式