成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java NIO淺析

yeooo / 1665人閱讀

摘要:阻塞請求結(jié)果返回之前,當前線程被掛起。也就是說在異步中,不會對用戶線程產(chǎn)生任何阻塞。當前線程在拿到此次請求結(jié)果的過程中,可以做其它事情。事實上,可以只用一個線程處理所有的通道。

準備知識 同步、異步、阻塞、非阻塞

同步和異步說的是服務(wù)端消息的通知機制,阻塞和非阻塞說的是客戶端線程的狀態(tài)。
已客戶端一次網(wǎng)絡(luò)請求為例做簡單說明:

同步
同步是指一次請求沒有得到結(jié)果之前就不返回。

異步
請求不會立刻得到最終結(jié)果,服務(wù)器處理完成再異步通知客戶端。

阻塞
請求結(jié)果返回之前,當前線程被掛起。在此期間不能做任何其他的事情。

非阻塞
請求立即返回,后續(xù)由客戶端時不時的詢問服務(wù)器結(jié)果或者服務(wù)器異步回調(diào)。

同步IO、異步IO、阻塞IO、非阻塞IO

通常來說,IO操作包括:對硬盤的讀寫、對socket的讀寫以及外設(shè)的讀寫。
已一個IO讀取過程為例做簡要說明(如圖):

DMA把數(shù)據(jù)讀取到內(nèi)核空間的緩沖區(qū)(讀就緒)

內(nèi)核將數(shù)據(jù)拷貝到用戶空間。

內(nèi)核空間是用戶代碼無法控制的,所以用戶空間在讀取之前,首先會判斷是否已經(jīng)讀就緒。

同步IO
當用戶發(fā)出IO請求操作之后,內(nèi)核會去查看要讀取的數(shù)據(jù)是否就緒,如果數(shù)據(jù)沒有就緒,就一直等待。需要通過用戶線程或者內(nèi)核不斷地去輪詢數(shù)據(jù)是否就緒,當數(shù)據(jù)就緒時,再將數(shù)據(jù)從內(nèi)核拷貝到用戶空間。

異步IO
只有IO請求操作的發(fā)出是由用戶線程來進行的,IO操作的兩個階段都是由內(nèi)核自動完成,然后發(fā)送通知告知用戶線程IO操作已經(jīng)完成。也就是說在異步IO中,不會對用戶線程產(chǎn)生任何阻塞。

阻塞IO
當用戶線程發(fā)起一個IO請求操作(以讀請求操作為例),內(nèi)核查看要讀取的數(shù)據(jù)還沒就緒,當前線程被掛起,阻塞等待結(jié)果返回。

非阻塞IO
如果數(shù)據(jù)沒有就緒,則會返回一個標志信息告知用戶線程當前要讀的數(shù)據(jù)沒有就緒。當前線程在拿到此次請求結(jié)果的過程中,可以做其它事情。

JAVA中的BIO、NIO、AIO

BIO
同步阻塞,傳統(tǒng)io方式。
適用于連接數(shù)目比較小且固定的架構(gòu),這種方式對服務(wù)器資源要求比較高,并發(fā)局限于應(yīng)用中。

NIO
同步非阻塞,jdk4開始支持。
適用于連接數(shù)目多且連接比較短(輕操作)的架構(gòu),比如聊天服務(wù)器。

AIO
異步非阻塞,jdk7開始支持。
適用于連接數(shù)目多且連接比較長(重操作)的架構(gòu)。

形象的理解NIO和AIO:
如果把內(nèi)核比作快遞,NIO就是你要自己時不時到官網(wǎng)查下快遞是否已經(jīng)到了你所在城市,然后自己去取快遞;AIO就是快遞員送貨上門了。

Linux下五種IO模型

阻塞I/O(blocking I/O)

非阻塞I/O (nonblocking I/O)

I/O復(fù)用(select 和poll) (I/O multiplexing)

信號驅(qū)動I/O (signal driven I/O (SIGIO))

異步I/O (asynchronous I/O (the POSIX aio_functions))

IO復(fù)用模型(IO多路復(fù)用)

簡言之,就是通過單個線程(進程)來管理多IO流。如圖:

IO多路復(fù)用避免阻塞在IO上,原本為多進程或多線程來接收多個連接的消息變?yōu)閱芜M程或單線程保存多個socket的狀態(tài)后輪詢處理。只有當某個socket讀寫就緒后,才真正調(diào)用實際的IO讀寫操作。這樣可以避免線程切換帶來的開銷。

實現(xiàn)IO多路復(fù)用需要函數(shù)來支持,就是你說的linux下的select、poll、epoll以及win下 iocp和BSD的kqueue。這幾個函數(shù)也會使進程阻塞,但是和阻塞I/O所不同的是,它可以同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O準備狀態(tài)進行檢測。

IO多路復(fù)用為何比非阻塞IO模型的效率高是因為在非阻塞IO中,不斷地詢問socket狀態(tài)是通過用戶線程去進行的,而在IO多路復(fù)用中,輪詢每個socket狀態(tài)是內(nèi)核在進行的,這個效率要比用戶線程要高的多。

五種IO模型以及select、poll、epoll的詳細介紹推薦大家看這篇文章
socket阻塞與非阻塞,同步與異步、I/O模型

理解Reactor和Proactor模式

在Reactor模式中,會先對每個client注冊感興趣的事件,然后有一個線程專門去輪詢每個client是否有事件發(fā)生,當有事件發(fā)生時(讀寫就緒),便順序處理每個事件,當所有事件處理完之后,便再轉(zhuǎn)去繼續(xù)輪詢,如圖所示:

從這里可以看出,多路復(fù)用IO就是采用Reactor模式。注意,上面的圖中展示的是順序處理每個事件,當然為了提高事件處理速度,可以通過多線程或者線程池的方式來處理事件。
在Proactor模式中,當檢測到有事件發(fā)生時,會新起一個異步操作,然后交由內(nèi)核線程去處理,當內(nèi)核線程完成IO操作之后,發(fā)送一個通知告知操作已完成,可以得知,異步IO模型采用的就是Proactor模式。

這部分摘選自:Java NIO:淺析I/O模型

Java NIO介紹

Channels and Buffers(通道和緩沖區(qū))
標準的IO基于字節(jié)流和字符流進行操作的,而NIO是基于通道(Channel)和緩沖區(qū)(Buffer)進行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫入到通道中。

Non-blocking IO(非阻塞IO)
Java NIO可以讓你非阻塞的使用IO,例如:當線程從通道讀取數(shù)據(jù)到緩沖區(qū)時,線程還是可以進行其他事情。當數(shù)據(jù)被寫入到緩沖區(qū)時,線程可以繼續(xù)處理它。從緩沖區(qū)寫入通道也類似。

Selectors(選擇器)
選擇器用于監(jiān)聽多個通道的事件(比如:連接打開,數(shù)據(jù)到達)。因此,單個的線程可以監(jiān)聽多個數(shù)據(jù)通道。

NIO與IO區(qū)別

? IO ? ? ? ? ? ? ? ? NIO
面向流? ? ?? ? 面向緩沖
阻塞IO ? ? ?? ? 非阻塞IO
? 無? ? ? ? ? ? ? ? 選擇器

Channel

Java NIO的通道類似流,但又有些不同:

既可以從通道中讀取數(shù)據(jù),又可以寫數(shù)據(jù)到通道。但流的讀寫通常是單向的。

通道可以異步地讀寫。

通道中的數(shù)據(jù)總是要先讀到一個Buffer,或者總是要從一個Buffer中寫入。

Channel的實現(xiàn)

FileChannel (從文件中讀寫數(shù)據(jù))

DatagramChannel (通過UDP讀寫網(wǎng)絡(luò)中的數(shù)據(jù))

SocketChannel (通過TCP讀寫網(wǎng)絡(luò)中的數(shù)據(jù))

ServerSocketChannel (可以監(jiān)聽新進來的TCP連接,像Web服務(wù)器那樣)

Buffer

Java NIO中的Buffer用于和NIO通道進行交互。如你所知,數(shù)據(jù)是從通道讀入緩沖區(qū),從緩沖區(qū)寫入到通道中的。

Buffer的基本用法

使用Buffer讀寫數(shù)據(jù)一般遵循以下四個步驟:

分配指定大小的buffer空間

寫入數(shù)據(jù)到Buffer

調(diào)用flip()方法

從Buffer中讀取數(shù)據(jù)

調(diào)用clear()方法或者compact()方法

當向buffer寫入數(shù)據(jù)時,buffer會記錄下寫了多少數(shù)據(jù)。一旦要讀取數(shù)據(jù),需要通過flip()方法將Buffer從寫模式切換到讀模式。在讀模式下,可以讀取之前寫入到buffer的所有數(shù)據(jù)。

一旦讀完了所有的數(shù)據(jù),就需要清空緩沖區(qū),讓它可以再次被寫入。有兩種方式能清空緩沖區(qū):調(diào)用clear()或compact()方法。clear()方法會清空整個緩沖區(qū)。compact()方法只會清除已經(jīng)讀過的數(shù)據(jù)。任何未讀的數(shù)據(jù)都被移到緩沖區(qū)的起始處,新寫入的數(shù)據(jù)將放到緩沖區(qū)未讀數(shù)據(jù)的后面。
注:Buffer中的數(shù)據(jù)并未清除,只是這些標記告訴我們可以從哪里開始往Buffer里寫數(shù)據(jù)。

Buffer的類型

ByteBuffer

MappedByteBuffer

CharBuffer

DoubleBuffer

FloatBuffer

IntBuffer

LongBuffer

ShortBuffer

Selector

Selector(選擇器)是Java NIO中能夠檢測一到多個通道,并能夠知曉通道是否為諸如讀寫事件做好準備的組件。這樣,一個多帶帶的線程可以管理多個channel,從而管理多個網(wǎng)絡(luò)連接。

為什么使用Selector?

僅用單個線程來處理多個Channels的好處是,只需要更少的線程來處理通道。事實上,可以只用一個線程處理所有的通道。對于操作系統(tǒng)來說,線程之間上下文切換的開銷很大,而且每個線程都要占用系統(tǒng)的一些資源(如內(nèi)存)。因此,使用的線程越少越好。

但是,需要記住,現(xiàn)代的操作系統(tǒng)和CPU在多任務(wù)方面表現(xiàn)的越來越好,所以多線程的開銷隨著時間的推移,變得越來越小了。實際上,如果一個CPU有多個內(nèi)核,不使用多任務(wù)可能是在浪費CPU能力。不管怎么說,關(guān)于那種設(shè)計的討論應(yīng)該放在另一篇不同的文章中。在這里,只要知道使用Selector能夠處理多個通道就足夠了。

NIO如何實現(xiàn)非阻塞?

服務(wù)器上所有Channel需要向Selector注冊,而Selector則負責(zé)監(jiān)視這些Socket的IO狀態(tài)(觀察者),當其中任意一個或者多個Channel具有可用的IO操作時,該Selector的select()方法將會返回大于0的整數(shù),該整數(shù)值就表示該Selector上有多少個Channel具有可用的IO操作,并提供了selectedKeys()方法來返回這些Channel對應(yīng)的SelectionKey集合(一個SelectionKey對應(yīng)一個就緒的通道)。正是通過Selector,使得服務(wù)器端只需要不斷地調(diào)用Selector實例的select()方法即可知道當前所有Channel是否有需要處理的IO操作。
注:java NIO就是多路復(fù)用IO,jdk7之后底層是epoll模型。

一個簡單的demo
/**
 * NioServer
 * Date: 6/27/2016
 * Time: 8:06 PM
 *
 * @author xiaodong.fan
 */
public class NioServer {

  public static void main(String[] args) throws Exception {
    // 1、初始化一個ServerSocketChannel
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 9999);
    serverSocketChannel.configureBlocking(false);// 設(shè)置為非阻塞模式,后續(xù)的accept()方法會立刻返回
    serverSocketChannel.socket().bind(inetSocketAddress, 1024);// 監(jiān)聽本地9999端口的請求,第二個參數(shù)限制可以建立的最大連接數(shù)
    Selector selector = Selector.open();
    /**
     * 將通道注冊到一個選擇器上(非阻塞模式與選擇器搭配會工作的更好)
     * 注意register()方法的第二個參數(shù)。這是一個“interest集合”,意思是在通過Selector監(jiān)聽Channel時對什么事件感興趣。
     * 可以監(jiān)聽四種不同類型的事件:OP_CONNECT,OP_ACCEPT,OP_READ,OP_WRITE
     * 如果你對不止一種事件感興趣,那么可以用“位或”操作符將常量連接起來:SelectionKey.OP_READ | SelectionKey.OP_WRITE
     */
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

    // 2、監(jiān)聽連接請求并處理
    while (true) {
      int connects = selector.select(2000);// 每次最多阻塞2秒

      if (connects == 0) {
        System.out.println("沒有請求...");
        continue;
      } else {
        System.out.println("請求來了...");
      }

      // 獲取監(jiān)聽到有連接請求的channel對應(yīng)的selectionKey
      Set selectedKeys = selector.selectedKeys();
      // 遍歷selectionKey來訪問就緒的通道
      Iterator selectedKeyIterator = selectedKeys.iterator();
      while (selectedKeyIterator.hasNext()) {
        SelectionKey selectionKey = selectedKeyIterator.next();
        if (selectionKey.isValid()) {

          if (selectionKey.isAcceptable()) {// 接收就緒
            ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
            // 返回一個包含新進來的連接SocketChannel,因為前面設(shè)置的非阻塞模式,這里會立即返回。
            SocketChannel socketChannel = channel.accept();
            if (socketChannel == null) {
              return;
            }
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);
            System.out.println("連接建立完成");
            doWrite(socketChannel, "connection is established");// 連接建立完成,給客戶端發(fā)消息

          } else if (selectionKey.isReadable()) {// 讀就緒

            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
            ByteBuffer readBuffer = ByteBuffer.allocate(10);
            while ((socketChannel.read(readBuffer)) > 0) {// // 讀取客戶端發(fā)送來的消息
              readBuffer.flip();
              byte[] bytes = new byte[readBuffer.remaining()];
              readBuffer.get(bytes);
              String body = new String(bytes, "utf-8");
              doWrite(socketChannel, body);// 將客戶端發(fā)送的內(nèi)容原封不動的發(fā)回去
              readBuffer.clear();
            }
            socketChannel.close();//讀取數(shù)據(jù)完畢后關(guān)閉連接,如果不關(guān)閉一直處于連接狀態(tài)。

          }
        }

        selectedKeyIterator.remove(); // 注意每次必須手動remove(),下次該通道變成就緒時,Selector會再次將其放入已選擇鍵集中
      }
    }
  }

  private static void doWrite(SocketChannel socketChannel, String response) throws IOException {
    if (StringUtils.isNotBlank(response)) {
      byte[] bytes = response.getBytes();
      ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
      writeBuffer.put(bytes);
      writeBuffer.flip();
      // 發(fā)送消息到客戶端
      socketChannel.write(writeBuffer);
      writeBuffer.clear();
    }

  }

}
參考文章

Java NIO:淺析I/O模型
socket阻塞與非阻塞,同步與異步、I/O模型
Java BIO、NIO、AIO 學(xué)習(xí)
Java NIO:NIO概述
Java NIO 系列教程
Java網(wǎng)絡(luò)編程——使用NIO實現(xiàn)非阻塞Socket通信

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

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/64851.html

相關(guān)文章

  • Asyncdb(一):寫一個純函數(shù)式的Mysql異步驅(qū)動

    摘要:相信你們也注意到了,但是應(yīng)該大多數(shù)人都不是很熟悉,它也是一個基于,使用編寫的,完全異步的數(shù)據(jù)庫驅(qū)動,同時支持和,其項目地址。 之前的Akka系列博客接下去可能并不會經(jīng)常更新了,但是后續(xù)看到一些好的點或者大家對哪些還是比較感興趣還會繼續(xù)寫幾篇,這里先跟大家說明一下。 背景 寫一個純函數(shù)式的Mysql異步驅(qū)動這個構(gòu)思是公司的一個大佬提的,這將會是一個開源項目,我也很有幸能夠參與其中,嘗試寫...

    chaosx110 評論0 收藏0
  • 關(guān)于Java IO與NIO知識都在這里

    摘要:從通道進行數(shù)據(jù)寫入創(chuàng)建一個緩沖區(qū),填充數(shù)據(jù),并要求通道寫入數(shù)據(jù)。三之通道主要內(nèi)容通道介紹通常來說中的所有都是從通道開始的。從中選擇選擇器維護注冊過的通道的集合,并且這種注冊關(guān)系都被封裝在當中停止選擇的方法方法和方法。 由于內(nèi)容比較多,我下面放的一部分是我更新在我的微信公眾號上的鏈接,微信排版比較好看,更加利于閱讀。每一篇文章下面我都把文章的主要內(nèi)容給列出來了,便于大家學(xué)習(xí)與回顧。 Ja...

    Riddler 評論0 收藏0
  • Java深入-框架技巧

    摘要:從使用到原理學(xué)習(xí)線程池關(guān)于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實現(xiàn)在軟件開發(fā)中,分散于應(yīng)用中多出的功能被稱為橫切關(guān)注點如事務(wù)安全緩存等。 Java 程序媛手把手教你設(shè)計模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經(jīng)風(fēng)雨慢慢變老,回首走過的點點滴滴,依然清楚的記得當初愛情萌芽的模樣…… Java 進階面試問題列表 -...

    chengtao1633 評論0 收藏0

發(fā)表評論

0條評論

yeooo

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<