国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

java同步非阻塞IO

caoym / 743人閱讀

摘要:的異步即是異步的,也是非阻塞的。但是,也可以進(jìn)行一層稍微薄點(diǎn)的封裝,保留這種多路復(fù)用的模型,比如的,是一種同步非阻塞的模型。系統(tǒng)調(diào)用操作系統(tǒng)的系統(tǒng)調(diào)用提供了多路復(fù)用的非阻塞的系統(tǒng)調(diào)用,這也是機(jī)制實(shí)現(xiàn)需要用到的。

異步IO編程在javascript中得到了廣泛的應(yīng)用,之前也寫過(guò)一篇博文進(jìn)行梳理。
js的異步IO即是異步的,也是非阻塞的。非阻塞的IO需要底層操作系統(tǒng)的支持,比如在linux上的epoll系統(tǒng)調(diào)用。

從另外一個(gè)角度看待的話,底層操作系統(tǒng)對(duì)于非阻塞IO的系統(tǒng)調(diào)用是一種多路復(fù)用機(jī)制,js對(duì)其進(jìn)行了比較厚的封裝,轉(zhuǎn)換成了異步IO。
但是,也可以進(jìn)行一層稍微薄點(diǎn)的封裝,保留這種多路復(fù)用的模型,比如java的NIO,是一種同步非阻塞的IO模型。
非阻塞IO的一大優(yōu)勢(shì)是,性能好,快啊!這在對(duì)IO性能要求高的場(chǎng)景得到了大量應(yīng)用,比如SOA框架。

傳統(tǒng)的同步阻塞IO 同步阻塞IO的特點(diǎn)

傳統(tǒng)的同步IO方式,比如網(wǎng)絡(luò)傳輸,比如文件IO,在調(diào)用者調(diào)用read()時(shí),調(diào)用會(huì)被一層一層調(diào)用下去直到OS的系統(tǒng)調(diào)用,調(diào)用者的線程會(huì)被阻塞。
當(dāng)讀取完成時(shí),該線程又會(huì)被喚醒,read()函數(shù)返回IO操作讀取的數(shù)據(jù)。

我們很容易能發(fā)現(xiàn)這種方式的特點(diǎn)及優(yōu)劣:

接口容易理解,編程難度低。對(duì)調(diào)用者而言,read()就像一個(gè)普通的函數(shù)調(diào)用一樣,返回讀取的數(shù)據(jù)。只不過(guò)可能這個(gè)操作有點(diǎn)慢,這個(gè)函數(shù)執(zhí)行時(shí)間長(zhǎng)了一些而已。

在費(fèi)時(shí)的IO操作時(shí),線程需要等待IO完成。這意味著,如果你需要多個(gè)IO操作同時(shí)進(jìn)行,就只能通過(guò)開多個(gè)線程來(lái)解決。

在客戶端編程時(shí),第二點(diǎn)這個(gè)問(wèn)題不大。客戶端程序?qū)O的并發(fā)要求不高,反而因?yàn)橥阶枞鸌O的接口易于編程而能夠減輕編程難度,代碼更直觀更可讀,從而變相的提高可調(diào)試性和開發(fā)效率。

服務(wù)端編程的特點(diǎn)

然而,在服務(wù)器端編程的時(shí)候,這個(gè)劣勢(shì)就很明顯了,服務(wù)器端程序可能會(huì)面臨大量并發(fā)IO的考驗(yàn)。
傳統(tǒng)的同步IO方式,比如說(shuō)socket編程,服務(wù)器端的一個(gè)簡(jiǎn)單的處理邏輯是這樣的:

使用一個(gè)線程監(jiān)聽端口,如有客戶端的TCP連接連入,就交由處理線程處理。

每來(lái)一個(gè)TCP連接,就需要開一個(gè)線程來(lái)處理和該客戶端的邏輯。

在實(shí)際場(chǎng)景中會(huì)有很多優(yōu)化技術(shù),比如使用線程池。然而線程池僅僅是將TCP連接放入一個(gè)隊(duì)列里交由線程池中空閑的線程處理。
實(shí)質(zhì)上,即使使用線程池,也改變不了正在被處理的每一個(gè)請(qǐng)求都需要占用一個(gè)多帶帶的線程這一事實(shí)。
這樣,會(huì)造成一些問(wèn)題:

每一個(gè)請(qǐng)求需要一個(gè)線程來(lái)處理,但是服務(wù)器的線程數(shù)量是有上限的,這就限制了服務(wù)器的并發(fā)量。

線程本身的調(diào)度也占用一定的操作系統(tǒng)資源,在線程比較多的情況下,這個(gè)占用疊加起來(lái)就非常客觀。

多路復(fù)用IO 概念及模型

java提供的NIO就是一種多路復(fù)用IO方式。
它能夠?qū)⒍鄠€(gè)IO操作用一個(gè)線程去管理,一個(gè)線程即可管理多個(gè)IO操作。

NIO的操作邏輯是這樣的,首先將需要監(jiān)控的IO操作注冊(cè)到某個(gè)地方,并由一個(gè)線程管理。
當(dāng)這些IO操作完成,會(huì)以事件的形式產(chǎn)生。該線程能夠獲取到完成的事件列表,并且對(duì)其進(jìn)行處理。

java的NIO中有三個(gè)重要的概念:

Channel通道。表示一種IO原始源。如ServerSocketChannel表示監(jiān)聽客戶端發(fā)起的TCP連接。
通過(guò)Channel能夠發(fā)起某種IO操作,但是卻立即返回不阻塞。

Buffer 緩沖區(qū)。Channel讀取或?qū)懭氲臄?shù)據(jù)必須通過(guò)Buffer。網(wǎng)絡(luò)讀寫常用的是ByteBuffer。

Selector 選擇器。NIO中最核心的東西,將Channel注冊(cè)到Selector中,使得Selector能夠監(jiān)控到該IO操作。
可以理解成Selecotr不斷輪詢被注冊(cè)的Channel,一旦Channel中有注冊(cè)的事件發(fā)生,便能處理發(fā)生的事件。

這里只是做個(gè)總結(jié),看下下面的示例代碼就明白了。

Selector和Channel
private void exec(int port) throws IOException {
    Selector selector = Selector.open();
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.socket().bind(new InetSocketAddress(port));
    serverSocketChannel.configureBlocking(false);
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

    while (true) {
        int n = selector.select(); // Block
        Iterator it = selector.selectedKeys().iterator();
        while (it.hasNext()) {
            SelectionKey key = it.next();
            if (key.isAcceptable()) {
                ServerSocketChannel server = (ServerSocketChannel) key.channel();
                SocketChannel channel = server.accept();
                if (channel != null) {
                    channel.configureBlocking(false);
                    channel.register(selector, SelectionKey.OP_READ);
                    onAccept(channel);
                }
            }
            if (key.isReadable()) {
                SocketChannel socketChannel = (SocketChannel) key.channel();
                onRead(socketChannel);
            }
            it.remove();
        }
    }
}

來(lái)一步一步的分析這些代碼。

首先,第3行到第6行是對(duì)通道ServerSocketChannel的操作。
對(duì)于這個(gè)ServerSocketChannel,首先是設(shè)定了它的監(jiān)聽地址,這個(gè)與傳統(tǒng)的阻塞IO一致,給定一些初始的數(shù)據(jù)。傳統(tǒng)的阻塞IO之后會(huì)調(diào)用socket.accept()來(lái)獲取客戶端連接的TCP連接,這是一個(gè)阻塞的方法。
但是NIO在這里把ServerSocketChannel注冊(cè)到了Selector上,并且監(jiān)控OP_ACCEPT事件。這個(gè)時(shí)候socket可以認(rèn)為已經(jīng)在監(jiān)聽了,但是沒(méi)有阻塞線程。
之后,如果有TCP連接連接上,OP_ACCEPT事件就會(huì)產(chǎn)生,通過(guò)selector即可處理該事件。
因此,NIO的操作邏輯其實(shí)是事件驅(qū)動(dòng)的。

后面的循環(huán)則是Selector處理的主邏輯。
第9行,這是一個(gè)阻塞的方法。它會(huì)等待被注冊(cè)的這些IO操作處理完成。一旦有一部分IO操作完成,它就會(huì)返回。
通過(guò)selector.selectedKeys()即可獲得完成的IO操作的事件。后面的代碼也就是在處理這些事件。
這部分完成的IO事件處理完畢后,就會(huì)循環(huán)的去處理下一批完成的IO事件,如此往復(fù)。
這里,我們可以清晰的看到,通過(guò)NIO的多路復(fù)用模型,我們通過(guò)一個(gè)線程,就能管理多個(gè)IO操作。

循環(huán)內(nèi)部處理的邏輯,key.isAcceptable()可以認(rèn)為是判斷該事件是否是OP_ACCEPT事件。是的話表示已經(jīng)有客戶端TCP連接連接上了,第15行獲取該TCP連接的socket對(duì)象。由于是NIO編程,這是獲取到的是SocketChannel對(duì)象。
之后將該對(duì)象的OP_READ注冊(cè)到Selector上,發(fā)起IO讀操作,并且讓Selector監(jiān)聽讀完成的事件。

后面的key.isReadable()也是同樣的道理,這里只有上面的代碼注冊(cè)了OP_READ事件,因此這里一定是上面的讀操作完成了產(chǎn)生的事件。

Buffer

上面的代碼里,當(dāng)有新的TCP連接連入時(shí),調(diào)用回調(diào)函數(shù)onAccept;當(dāng)對(duì)方傳輸數(shù)據(jù)給自己時(shí),數(shù)據(jù)讀取完成后,調(diào)用回調(diào)函數(shù)onRead

下面是這兩個(gè)回調(diào)函數(shù)的實(shí)現(xiàn),它的功能很簡(jiǎn)單:

當(dāng)有TCP連接第一次連入時(shí),發(fā)送hello 給對(duì)方。

當(dāng)接收到對(duì)方傳來(lái)的數(shù)據(jù)時(shí),原封不動(dòng)的送回去。大概算是一個(gè)echo服務(wù)器。

private void onRead(SocketChannel socketChannel) throws IOException {
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    int count;
    while ((count = socketChannel.read(buffer)) > 0) {
        buffer.flip();
        while (buffer.hasRemaining()) {
            socketChannel.write(buffer);
        }
        buffer.clear();
    }

    if (count < 0) {
        socketChannel.close();
    }
}

private void onAccept(SocketChannel channel) throws IOException {
    System.out.println(channel.socket().getInetAddress() + "/" + channel.socket().getPort());
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    buffer.put("hello
".getBytes());
    buffer.flip();
    channel.write(buffer);
}

從上面的代碼可以看出:

onRead中的讀操作是非阻塞的。在之前數(shù)據(jù)的網(wǎng)絡(luò)傳輸已經(jīng)完成了,這里只是處理傳輸完成的數(shù)據(jù)而已。

至于這里的寫操作是不是阻塞的。。。我覺(jué)得不是阻塞的,這一點(diǎn)我還不確定 ,時(shí)間有限,之后會(huì)經(jīng)過(guò)代碼驗(yàn)證,查更多資料去確認(rèn)這一點(diǎn)。

所有的讀寫操作的數(shù)據(jù)都需要經(jīng)過(guò)Buffer。那為什么要增加Buffer這一抽象概念?直接使用bytes[]不挺好嗎?
我猜測(cè)和NIO底層原理有關(guān)系,可能OS將數(shù)據(jù)傳輸?shù)搅瞬僮飨到y(tǒng)原生的內(nèi)存里,java使用的話復(fù)制到j(luò)vm內(nèi)存中。我也不確定。。。 將來(lái)查更多資料去完善這一疑惑吧。

DEMO效果

上面通過(guò)一個(gè)小DEMO,也就是一個(gè)簡(jiǎn)單的ECHO服務(wù)器演示了NIO編程。下面來(lái)測(cè)試下結(jié)果:

frapples:~ ?> nc -nvv 127.0.0.1 4040
Connection to 127.0.0.1 4040 port [tcp/*] succeeded!
hello
jfldjfl
jfldjfl
jfldjflieu
jfldjflieu
jfldhgldjfljdl
jfldhgldjfljdl

效果不錯(cuò)!不過(guò)這還沒(méi)完。
嘗試開啟多個(gè)終端,同時(shí)連接服務(wù)器,你會(huì)驚訝的發(fā)現(xiàn),服務(wù)器能夠完美的同時(shí)和多個(gè)客戶端連接而不會(huì)出現(xiàn)“卡死”的情況。
回顧剛才的小DEMO我們可以發(fā)現(xiàn),剛才的DEMO是 單線程 的,但是通過(guò)多路復(fù)用模型,卻能同時(shí)處理多個(gè)IO操作。

底層原理 硬件機(jī)制

之前在博文《異步IO和同步IO》中也提到了一些異步IO的操作系統(tǒng)機(jī)制。
非阻塞IO需要操作系統(tǒng)機(jī)制的支持,在linux系統(tǒng)上,對(duì)應(yīng)的是select/poll系統(tǒng)調(diào)用或epoll系統(tǒng)調(diào)用。

操作系統(tǒng)的作用之一是對(duì)硬件設(shè)備的管理,我們發(fā)現(xiàn),負(fù)責(zé)運(yùn)算的部件CPU和負(fù)責(zé)網(wǎng)絡(luò)傳輸?shù)牟考W(wǎng)卡,它們是互相獨(dú)立的,因此,它們實(shí)際上可以同時(shí)執(zhí)行任務(wù)。那么,底層硬件的支持使得完全可以做到以下步驟:

CPU發(fā)送給網(wǎng)卡某些網(wǎng)絡(luò)IO操作請(qǐng)求,網(wǎng)卡接收到CPU接收到的請(qǐng)求。

網(wǎng)卡處理接收到的網(wǎng)絡(luò)IO操作任務(wù),于此同時(shí),CPU也能執(zhí)行其它的計(jì)算工作。

當(dāng)網(wǎng)卡的網(wǎng)絡(luò)IO操作完成后,通過(guò)硬件中斷機(jī)制給CPU發(fā)中斷。

CPU執(zhí)行中斷處理程序,執(zhí)行IO操作完成后的邏輯。

這里有個(gè)小小的問(wèn)題,在讀取數(shù)據(jù)的時(shí)候,上面的步驟網(wǎng)卡讀取數(shù)據(jù)時(shí)顯然是不通過(guò)CPU的。以我個(gè)人有限的硬件知識(shí)推測(cè),非阻塞IO的機(jī)制可能需要用到DMA。
仍然是個(gè)人推測(cè),以后有時(shí)間去查閱相關(guān)資料去解決這個(gè)疑惑。

我們可以看到,硬件的運(yùn)作方式天然就是異步的,也因此,操作系統(tǒng)也非常容易基于此進(jìn)行抽象和封裝,向上提供非阻塞的IO系統(tǒng)調(diào)用。

OS系統(tǒng)調(diào)用

linux操作系統(tǒng)的系統(tǒng)調(diào)用提供了多路復(fù)用的非阻塞IO的系統(tǒng)調(diào)用,這也是java NIO機(jī)制實(shí)現(xiàn)需要用到的。
在linux2.6之前,采用select/poll系統(tǒng)調(diào)用實(shí)現(xiàn),而在linux2.6之后,采用epoll實(shí)現(xiàn),使用紅黑樹優(yōu)化過(guò),也因此性能更高。

最后

本篇博文梳理的java的NIO機(jī)制,這是一種多路復(fù)用模型,能夠使用一個(gè)線程去管理多個(gè)IO操作,避免傳統(tǒng)同步IO的線程開銷,大大提升性能。

從我個(gè)人的觀點(diǎn),評(píng)判一種模型是否易用,一方面來(lái)看該模型是否與實(shí)際的問(wèn)題特點(diǎn)相契合;另外一方面,看該模型需要開發(fā)者花多少成本在模型本身上而非業(yè)務(wù)邏輯上。
從這個(gè)標(biāo)準(zhǔn)出發(fā),我們也不難發(fā)現(xiàn),本身異步IO的回調(diào)方式就夠讓開發(fā)者頭疼的了,然而和異步IO相比,NIO比異步IO還要麻煩。
你需要花大量精力去時(shí)間去處理,去理解NIO本身的邏輯。因此,NIO的缺點(diǎn)是較高的開發(fā)成本和較晦澀的代碼,不優(yōu)雅。

NIO在SOA框架,RPC框架等服務(wù)器領(lǐng)域有著較大的應(yīng)用,除了java標(biāo)準(zhǔn)庫(kù)的NIO之外,這些實(shí)際生產(chǎn)的框架多使用第三方的NIO框架Netty。
原因之一是,java標(biāo)準(zhǔn)庫(kù)的NIO有一個(gè)bug,可能造成CPU 100%的占用。

感謝

今天,是我在公司實(shí)習(xí)呆的最后一天,我花了一個(gè)下午的時(shí)間去組織這篇博文。
感謝我的老大對(duì)我的器重和信任,給予我很多的機(jī)會(huì)去鍛煉,也給予了我很大的自由空間去研究技術(shù),自我提升。
也感謝這段時(shí)間對(duì)我照顧,給予我?guī)椭耐聜儯8D銈儯?/p>

注:該文于2018-04-13撰寫于我的github靜態(tài)頁(yè)博客,現(xiàn)同步到我的segmentfault來(lái)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/71455.html

相關(guān)文章

  • Java NIO淺析

    摘要:阻塞請(qǐng)求結(jié)果返回之前,當(dāng)前線程被掛起。也就是說(shuō)在異步中,不會(huì)對(duì)用戶線程產(chǎn)生任何阻塞。當(dāng)前線程在拿到此次請(qǐng)求結(jié)果的過(guò)程中,可以做其它事情。事實(shí)上,可以只用一個(gè)線程處理所有的通道。 準(zhǔn)備知識(shí) 同步、異步、阻塞、非阻塞 同步和異步說(shuō)的是服務(wù)端消息的通知機(jī)制,阻塞和非阻塞說(shuō)的是客戶端線程的狀態(tài)。已客戶端一次網(wǎng)絡(luò)請(qǐng)求為例做簡(jiǎn)單說(shuō)明: 同步同步是指一次請(qǐng)求沒(méi)有得到結(jié)果之前就不返回。 異步請(qǐng)求不會(huì)...

    yeooo 評(píng)論0 收藏0
  • Netty序章之BIO NIO AIO演變

    摘要:后改良為用線程池的方式代替新增線程,被稱為偽異步。最大的問(wèn)題是阻塞,同步。每次請(qǐng)求都由程序執(zhí)行并返回,這是同步的缺陷。這些都會(huì)被注冊(cè)在多路復(fù)用器上。多路復(fù)用器提供選擇已經(jīng)就緒狀態(tài)任務(wù)的能力。并沒(méi)有采用的多路復(fù)用器,而是使用異步通道的概念。 Netty是一個(gè)提供異步事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用框架,用以快速開發(fā)高性能、高可靠的網(wǎng)絡(luò)服務(wù)器和客戶端程序。Netty簡(jiǎn)化了網(wǎng)絡(luò)程序的開發(fā),是很多框架和公司...

    VincentFF 評(píng)論0 收藏0
  • Netty序章之BIO NIO AIO演變

    摘要:后改良為用線程池的方式代替新增線程,被稱為偽異步。最大的問(wèn)題是阻塞,同步。每次請(qǐng)求都由程序執(zhí)行并返回,這是同步的缺陷。這些都會(huì)被注冊(cè)在多路復(fù)用器上。多路復(fù)用器提供選擇已經(jīng)就緒狀態(tài)任務(wù)的能力。并沒(méi)有采用的多路復(fù)用器,而是使用異步通道的概念。 Netty是一個(gè)提供異步事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用框架,用以快速開發(fā)高性能、高可靠的網(wǎng)絡(luò)服務(wù)器和客戶端程序。Netty簡(jiǎn)化了網(wǎng)絡(luò)程序的開發(fā),是很多框架和公司...

    CntChen 評(píng)論0 收藏0
  • 好記性不如爛筆頭——NIO學(xué)習(xí)筆記

    摘要:該線程在此期間不能再干任何事情了。線程通訊線程之間通過(guò)等方式通訊。選擇器傳統(tǒng)的模式會(huì)基于服務(wù)器會(huì)為每個(gè)客戶端請(qǐng)求建立一個(gè)線程由該線程單獨(dú)負(fù)貴處理一個(gè)客戶請(qǐng)求。 本文是對(duì)NIO知識(shí)的歸納與整理 1.阻塞與同步 1)阻塞(Block)和非租塞(NonBlock): 阻塞和非阻塞是進(jìn)程在訪問(wèn)數(shù)據(jù)的時(shí)候,數(shù)據(jù)是否準(zhǔn)備就緒的一種處理方式,當(dāng)數(shù)據(jù)沒(méi)有準(zhǔn)備的時(shí)候阻塞:往往需要等待缞沖區(qū)中的數(shù)據(jù)準(zhǔn)備好...

    王偉廷 評(píng)論0 收藏0
  • Java IO的一些思考

    摘要:最近面試問(wèn)的比較多的問(wèn)題就是這一塊了,有些也答出來(lái)了,有些答的不好,最近這段時(shí)間開始深入了解一些這方面的東西,也想總結(jié)一下。 最近面試問(wèn)的比較多的問(wèn)題就是IO這一塊了,有些也答出來(lái)了,有些答的不好,最近這段時(shí)間開始深入了解一些這方面的東西,也想總結(jié)一下。 前置點(diǎn) 1,用戶空間系統(tǒng)空間 Linux系統(tǒng)會(huì)把一個(gè)進(jìn)程分為兩個(gè)空間,用戶空間和系統(tǒng)空間,比如我們正常的編碼,操作的都是用戶空...

    468122151 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<