摘要:最近在學(xué)習(xí)網(wǎng)絡(luò)編程和相關(guān)的知識(shí),了解到是模式的網(wǎng)絡(luò)框架,但是提供了不同的來(lái)支持不同模式的網(wǎng)絡(luò)通信處理,包括同步異步阻塞和非阻塞。因?yàn)榈陌姹臼褂玫牡哪J?,而則希望使用模式,而且版本沒有將的部分配置項(xiàng)暴露出來(lái),比如說(shuō)和。
??最近在學(xué)習(xí)Java網(wǎng)絡(luò)編程和Netty相關(guān)的知識(shí),了解到Netty是NIO模式的網(wǎng)絡(luò)框架,但是提供了不同的Channel來(lái)支持不同模式的網(wǎng)絡(luò)通信處理,包括同步、異步、阻塞和非阻塞。學(xué)習(xí)要從基礎(chǔ)開始,所以我們就要先了解一下相關(guān)的基礎(chǔ)概念和Java原生的NIO。這里,就將最近我學(xué)習(xí)的知識(shí)總結(jié)一下,以供大家了解。
?為了節(jié)約你的時(shí)間,本文主要內(nèi)容如下:
異步,阻塞的概念
操作系統(tǒng)I/O的類型
Java NIO的底層實(shí)現(xiàn)
異步,同步,阻塞,非阻塞?同步和異步關(guān)注的是消息通信機(jī)制,所謂同步就是調(diào)用者進(jìn)行調(diào)用后,在沒有得到結(jié)果之前,該調(diào)用一直不會(huì)返回,但是一旦調(diào)用返回,就得到了返回值,同步就是指調(diào)用者主動(dòng)等待調(diào)用結(jié)果;而異步則相反,執(zhí)行調(diào)用之后直接返回,所以可能沒有返回值,等到有返回值時(shí),由被調(diào)用者通過狀態(tài),通知來(lái)通知調(diào)用者.異步就是指被調(diào)用者來(lái)通知調(diào)用者調(diào)用結(jié)果就緒.所以,二者在消息通信機(jī)制上有所不同,一個(gè)是調(diào)用者檢查調(diào)用結(jié)果是否就緒,一個(gè)是被調(diào)用者通知調(diào)用者結(jié)果就緒
?阻塞和非阻塞關(guān)注的是程序在等待調(diào)用結(jié)果(消息,返回值)時(shí)的狀態(tài).阻塞調(diào)用是指在調(diào)用結(jié)果返回之前,當(dāng)前線程會(huì)被掛起,調(diào)用線程只有在得到結(jié)果之后才會(huì)繼續(xù)執(zhí)行.非阻塞調(diào)用是指在不能立刻得到結(jié)構(gòu)之前,調(diào)用線程不會(huì)被掛起,還是可以執(zhí)行其他事情.
?兩組概念相互組合就有四種情況,分別是同步阻塞,同步非阻塞,異步阻塞,異步非阻塞.我們來(lái)舉個(gè)例子來(lái)分別類比上訴四種情況.
?比如你要從網(wǎng)上下載一個(gè)1G的文件,按下下載按鈕之后,如果你一直在電腦旁邊,等待下載結(jié)束,這種情況就是同步阻塞;如果你不需要一直呆在電腦旁邊,你可以去看一會(huì)書,但是你還是隔一段時(shí)間來(lái)查看一下下載進(jìn)度,這種情況就是同步非阻塞;如果你一直在電腦旁邊,但是下載器在下載結(jié)束之后會(huì)響起音樂來(lái)提醒你,這就是異步阻塞;但是如果你不呆在電腦旁邊,去看書,下載器下載結(jié)束后響起音樂來(lái)提醒你,那么這種情況就是異步非阻塞.
?知道上述兩組概念之后,我們來(lái)看一下Unix下可用的5種I/O模型:
阻塞I/O(bloking IO)
非阻塞I/O(nonblocking IO)
多路復(fù)用I/O(IO multiplexing)
信號(hào)驅(qū)動(dòng)I/O(signal driven IO)
異步I/O(asynchronous IO)
?前4種都是同步,只有最后一種是異步I/O.需要注意的是Java NIO依賴于Unix系統(tǒng)的多路復(fù)用I/O,對(duì)于I/O操作來(lái)說(shuō),它是同步I/O,但是對(duì)于編程模型來(lái)說(shuō),它是異步網(wǎng)絡(luò)調(diào)用.下面我們就以系統(tǒng)read的調(diào)用來(lái)介紹不同的I/O類型.
?當(dāng)一個(gè)read發(fā)生時(shí),它會(huì)經(jīng)歷兩個(gè)階段:
1 等待數(shù)據(jù)準(zhǔn)備
2 將數(shù)據(jù)從內(nèi)核內(nèi)存空間拷貝到進(jìn)程內(nèi)存空間中
?不同的I/O類型,在這兩個(gè)階段中有不同的行為.但是由于這塊內(nèi)容比較多,而且多為表述性的知識(shí),所以這里我們只給出幾張圖片來(lái)解釋,感覺興趣的同學(xué)可以去具體了解一下。
Java NIO的底層實(shí)現(xiàn)?我們都知道Netty通過JNI的方式提供了Native Socket Transport,為什么Netty要提供自己的Native版本的NIO呢?明明Java NIO底層也是基于epoll調(diào)用(最新的版本)的.這里,我們先不明說(shuō),大家想一想可能的情況.下列的源碼都來(lái)自于OpenJDK-8u40-b25版本.
open方法?如果我們順著Selector.open()方法一個(gè)類一個(gè)類的找下去,很容易就發(fā)現(xiàn)Selector的初始化是由DefaultSelectorProvider根據(jù)不同操作系統(tǒng)平臺(tái)生成的不同的SelectorProvider,對(duì)于Linux系統(tǒng),它會(huì)生成EPollSelectorProvider實(shí)例,而這個(gè)實(shí)例會(huì)生成EPollSelectorImpl作為最終的Selector實(shí)現(xiàn).
class EPollSelectorImpl extends SelectorImpl { ..... // The poll object EPollArrayWrapper pollWrapper; ..... EPollSelectorImpl(SelectorProvider sp) throws IOException { ..... pollWrapper = new EPollArrayWrapper(); pollWrapper.initInterrupt(fd0, fd1); ..... } ..... }
?EpollArrayWapper將Linux的epoll相關(guān)系統(tǒng)調(diào)用封裝成了native方法供EpollSelectorImpl使用.
private native int epollCreate(); private native void epollCtl(int epfd, int opcode, int fd, int events); private native int epollWait(long pollAddress, int numfds, long timeout, int epfd) throws IOException;
?上述三個(gè)native方法就對(duì)應(yīng)Linux下epoll相關(guān)的三個(gè)系統(tǒng)調(diào)用
//創(chuàng)建一個(gè)epoll句柄,size是這個(gè)監(jiān)聽的數(shù)目的最大值. int epoll_create(int size); //事件注冊(cè)函數(shù),告訴內(nèi)核epoll監(jiān)聽什么類型的事件,參數(shù)是感興趣的事件類型,回調(diào)和監(jiān)聽的fd int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //等待事件的產(chǎn)生,類似于select調(diào)用,events參數(shù)用來(lái)從內(nèi)核得到事件的集合 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
?所以,我們會(huì)發(fā)現(xiàn)在EpollArrayWapper的構(gòu)造函數(shù)中調(diào)用了epollCreate方法,創(chuàng)建了一個(gè)epoll的句柄.這樣,Selector對(duì)象就算創(chuàng)造完畢了.
register方法?與open類似,ServerSocketChannel的register函數(shù)底層是調(diào)用了SelectorImpl類的register方法,這個(gè)SelectorImpl就是EPollSelectorImpl的父類.
protected final SelectionKey register(AbstractSelectableChannel ch, int ops, Object attachment) { if (!(ch instanceof SelChImpl)) throw new IllegalSelectorException(); //生成SelectorKey來(lái)存儲(chǔ)到hashmap中,一共之后獲取 SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this); //attach用戶想要存儲(chǔ)的對(duì)象 k.attach(attachment); //調(diào)用子類的implRegister方法 synchronized (publicKeys) { implRegister(k); } //設(shè)置關(guān)注的option k.interestOps(ops); return k; }
?EpollSelectorImpl的相應(yīng)的方法實(shí)現(xiàn)如下,它調(diào)用了EPollArrayWrapper的add方法,記錄下Channel所對(duì)應(yīng)的fd值,然后將ski添加到keys變量中.在EPollArrayWrapper中有一個(gè)byte數(shù)組eventLow記錄所有的channel的fd值.
protected void implRegister(SelectionKeyImpl ski) { if (closed) throw new ClosedSelectorException(); SelChImpl ch = ski.channel; //獲取Channel所對(duì)應(yīng)的fd,因?yàn)樵趌inux下socket會(huì)被當(dāng)作一個(gè)文件,也會(huì)有fd int fd = Integer.valueOf(ch.getFDVal()); fdToKey.put(fd, ski); //調(diào)用pollWrapper的add方法,將channel的fd添加到監(jiān)控列表中 pollWrapper.add(fd); //保存到HashSet中,keys是SelectorImpl的成員變量 keys.add(ski); }
?我們會(huì)發(fā)現(xiàn),調(diào)用register方法并沒有涉及到EpollArrayWrapper中的native方法epollCtl的調(diào)用,這是因?yàn)樗麄儗⑦@個(gè)方法的調(diào)用推遲到Select方法中去了.
Select方法?和register方法類似,SelectorImpl中的select方法最終調(diào)用了其子類EpollSelectorImpl的doSelect方法
protected int doSelect(long timeout) throws IOException { ..... try { .... //調(diào)用了poll方法,底層調(diào)用了native的epollCtl和epollWait方法 pollWrapper.poll(timeout); } finally { .... } .... //更新selectedKeys,為之后的selectedKeys函數(shù)做準(zhǔn)備 int numKeysUpdated = updateSelectedKeys(); .... return numKeysUpdated; }
?由上述的代碼,可以看到,EPollSelectorImpl先調(diào)用EPollArrayWapper的poll方法,然后在更新SelectedKeys.其中poll方法會(huì)先調(diào)用epollCtl來(lái)注冊(cè)先前在register方法中保存的Channel的fd和感興趣的事件類型,然后epollWait方法等待感興趣事件的生成,導(dǎo)致線程阻塞.
int poll(long timeout) throws IOException { updateRegistrations(); ////先調(diào)用epollCtl,更新關(guān)注的事件類型 ////導(dǎo)致阻塞,等待事件產(chǎn)生 updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd); ..... return updated; }
?等待關(guān)注的事件產(chǎn)生之后(或在等待時(shí)間超過預(yù)先設(shè)置的最大時(shí)間),epollWait函數(shù)就會(huì)返回.select函數(shù)從阻塞狀態(tài)恢復(fù).
selectedKeys方法?我們先來(lái)看SelectorImpl中的selectedKeys方法.
//是通過Util.ungrowableSet生成的,不能添加,只能減少 private SetpublicSelectedKeys; public Set selectedKeys() { .... return publicSelectedKeys; }
?很奇怪啊,怎麼直接就返回publicSelectedKeys了,難道在select函數(shù)的執(zhí)行過程中有修改過這個(gè)變量嗎?
?publicSelectedKeys這個(gè)對(duì)象其實(shí)是selectedKeys變量的一份副本,你可以在SelectorImpl的構(gòu)造函數(shù)中找到它們倆的關(guān)系,我們?cè)倩仡^看一下select中updateSelectedKeys方法.
private int updateSelectedKeys() { //更新了的keys的個(gè)數(shù),或在說(shuō)是產(chǎn)生的事件的個(gè)數(shù) int entries = pollWrapper.updated; int numKeysUpdated = 0; for (int i=0; i后記 ?看到這里,詳細(xì)大家都已經(jīng)了解到了NIO的底層實(shí)現(xiàn)了吧.這里我想在說(shuō)兩個(gè)問題.
?一是為什么Netty自己又從新實(shí)現(xiàn)了一邊native相關(guān)的NIO底層方法? 聽聽Netty的創(chuàng)始人是怎麼說(shuō)的吧鏈接。因?yàn)镴ava的版本使用的epoll的level-triggered模式,而Netty則希望使用edge-triggered模式,而且Java版本沒有將epoll的部分配置項(xiàng)暴露出來(lái),比如說(shuō)TCP_CORK和SO_REUSEPORT。
?二是看這么多源碼,花費(fèi)這么多時(shí)間有什么作用呢?我感覺如果從非功利的角度來(lái)看,那么就是純粹的希望了解的更多,有時(shí)候看完源碼或在理解了底層原理之后,都會(huì)用一種恍然大悟的感覺,比如說(shuō)AQS的原理.如果從目的性的角度來(lái)看,那么就是你知道底層原理之后,你的把握性就更強(qiáng)了,如果出了問題,你可以更快的找出來(lái),并且解決.除此之外,你還可以按照具體的現(xiàn)實(shí)情況,以源碼為模板在自己造輪子,實(shí)現(xiàn)一個(gè)更加符合你當(dāng)前需求的版本.
?后續(xù)如果有時(shí)間,我希望好好了解一下epoll的操作系統(tǒng)級(jí)別的實(shí)現(xiàn)原理.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/72869.html
摘要:表示的是兩個(gè),當(dāng)其中任意一個(gè)計(jì)算完并發(fā)編程之是線程安全并且高效的,在并發(fā)編程中經(jīng)常可見它的使用,在開始分析它的高并發(fā)實(shí)現(xiàn)機(jī)制前,先講講廢話,看看它是如何被引入的。電商秒殺和搶購(gòu),是兩個(gè)比較典型的互聯(lián)網(wǎng)高并發(fā)場(chǎng)景。 干貨:深度剖析分布式搜索引擎設(shè)計(jì) 分布式,高可用,和機(jī)器學(xué)習(xí)一樣,最近幾年被提及得最多的名詞,聽名字多牛逼,來(lái),我們一步一步來(lái)?yè)羝魄皟蓚€(gè)名詞,今天我們首先來(lái)說(shuō)說(shuō)分布式。 探究...
摘要:表示的是兩個(gè),當(dāng)其中任意一個(gè)計(jì)算完并發(fā)編程之是線程安全并且高效的,在并發(fā)編程中經(jīng)??梢娝氖褂?,在開始分析它的高并發(fā)實(shí)現(xiàn)機(jī)制前,先講講廢話,看看它是如何被引入的。電商秒殺和搶購(gòu),是兩個(gè)比較典型的互聯(lián)網(wǎng)高并發(fā)場(chǎng)景。 干貨:深度剖析分布式搜索引擎設(shè)計(jì) 分布式,高可用,和機(jī)器學(xué)習(xí)一樣,最近幾年被提及得最多的名詞,聽名字多牛逼,來(lái),我們一步一步來(lái)?yè)羝魄皟蓚€(gè)名詞,今天我們首先來(lái)說(shuō)說(shuō)分布式。 探究...
摘要:表示的是兩個(gè),當(dāng)其中任意一個(gè)計(jì)算完并發(fā)編程之是線程安全并且高效的,在并發(fā)編程中經(jīng)常可見它的使用,在開始分析它的高并發(fā)實(shí)現(xiàn)機(jī)制前,先講講廢話,看看它是如何被引入的。電商秒殺和搶購(gòu),是兩個(gè)比較典型的互聯(lián)網(wǎng)高并發(fā)場(chǎng)景。 干貨:深度剖析分布式搜索引擎設(shè)計(jì) 分布式,高可用,和機(jī)器學(xué)習(xí)一樣,最近幾年被提及得最多的名詞,聽名字多牛逼,來(lái),我們一步一步來(lái)?yè)羝魄皟蓚€(gè)名詞,今天我們首先來(lái)說(shuō)說(shuō)分布式。 探究...
摘要:在中一般來(lái)說(shuō)通過來(lái)創(chuàng)建所需要的線程池,如高并發(fā)原理初探后端掘金閱前熱身為了更加形象的說(shuō)明同步異步阻塞非阻塞,我們以小明去買奶茶為例。 AbstractQueuedSynchronizer 超詳細(xì)原理解析 - 后端 - 掘金今天我們來(lái)研究學(xué)習(xí)一下AbstractQueuedSynchronizer類的相關(guān)原理,java.util.concurrent包中很多類都依賴于這個(gè)類所提供的隊(duì)列式...
摘要:在中一般來(lái)說(shuō)通過來(lái)創(chuàng)建所需要的線程池,如高并發(fā)原理初探后端掘金閱前熱身為了更加形象的說(shuō)明同步異步阻塞非阻塞,我們以小明去買奶茶為例。 AbstractQueuedSynchronizer 超詳細(xì)原理解析 - 后端 - 掘金今天我們來(lái)研究學(xué)習(xí)一下AbstractQueuedSynchronizer類的相關(guān)原理,java.util.concurrent包中很多類都依賴于這個(gè)類所提供的隊(duì)列式...
閱讀 2467·2021-11-08 13:13
閱讀 1402·2021-10-09 09:41
閱讀 1878·2021-09-02 15:40
閱讀 3347·2021-08-17 10:13
閱讀 2683·2019-08-29 16:33
閱讀 3281·2019-08-29 13:17
閱讀 3270·2019-08-29 11:00
閱讀 3433·2019-08-26 13:40