IO:阻塞和非阻塞、同步和異步 阻塞和非阻塞 在IO中的體現

阻塞和非阻塞

阻塞的時候線程會被掛起

阻塞

當數據還沒準備好時,調用了阻塞的方法,則線程會被掛起,會讓出CPU時間片,此時是無法處理過來的請求,需要等待其他線程來進行喚醒,該線程才能進行後續操作或者處理其他請求。

非阻塞

意味着,當數據還沒準備好的時候,即便我調用了阻塞方法,該線程也不會被掛起,後續的請求也能夠被處理。

同步

同步和異步跟串行和並行非常形似。

假設在一個場景下:完成一個大任務需要4個小任務。

同步的做法:需要依次4個步驟,注意這裏是依次,也就是說完成這個步驟,需要先完成前置步驟,也就是說下一個步驟是要看上一個步驟的執行結果。

異步的做法:可以同時進行4個步驟,無需等待其他步驟的執行結果。

阻塞和同步的最本質差別在於:

即便是同步,在等待的過程中,線程是不會被掛起,也不需要讓出CPU時間片的,

在IO中的體現

網絡編程的基本模型是:Client/Server模型

兩個進程之間要相互通信,其中服務端需要提供位置信息,讓客戶端找到自己。服務端提供IP地址和監聽的端口。

客戶端拿着這些信息去向服務端發起建立連接請求,通過三次握手成功建立連接後,客戶端就可以通過socket向服務器發送和接受消息。

BIO

BIO通信模型採用的是典型的:一請求一應答通信模型

採用BIO通信模型的服務端,通常會由一個獨立的Acceptor線程負責監聽客戶端的連接。

他不負責處理請求,他只是起到一個委派工作的作用,當他接收到請求之後,會爲每個客戶端創建一個新的線程進行鏈路處理

處理完之後,通過輸出流,返回應答給客戶端,然後線程被銷燬,資源被回收

該模型的最大問題就是缺乏彈性伸縮能力服務端的線程個數和客戶端的併發訪問數1:1的關係。

由於線程是Java虛擬機非常寶貴的資源,當線程書膨脹之後,系統的性能會隨着併發量增加呈正比的趨勢下降。

而且會有OOM的風險,當沒有內存空間創建線程時,就無法處理客戶端請求,最終導致進程宕機或卡死,無法對外提供服務。

最大的問題就是:每當有一個客戶端請求接入時,就會創建一個線程來處理請求

爲了改進這個一線程一連接模型,後面又演進出通過:

  • 線程池
  • 消息隊列

來實現1個或者多個線程處理N個客戶端的模型

在這裏,無論是線程池和消息隊列,都是解決內存空間線程的問題,並沒有實質性地改變同步阻塞通信本質問題

所以這種優化版本的BIO也被稱爲是僞異步

僞異步IO

採用線程池任務隊列可以實現一種:僞異步的IO通信

將客戶端的請求封裝成一個Task(該任務實現java.lang.Runnable接口),投遞到消息隊列中。

如果通過線程池維護一堆處理線程,去消費隊列中的消息。

處理完畢之後,再去通過客戶端就可以了,他的資源是可控的,無論客戶端的請求量是多少,也不會發生變化,同樣這也是他的缺點之一。

建立連接的accpet方法、讀取數據的read方法都是阻塞

這就意味着,如果有一方處理請求或者發出請求的比較慢,或者是網絡傳輸比較慢,那麼都會影響對方。

當調用OutputStreamwrite方法寫輸出流的時候,它將會被阻塞,直到所有要發送的字節全部寫入完畢,或者發生異常。

在TCP/IP中,當消息的接收方處理緩慢的時候,由於消息滑動窗口的存在,那麼它的接收窗口就會變小,就是那個TCP window size

如果這裏採用同步阻塞IO,並且write操作被阻塞很久,直到TCP window size 大於0或者發生IO異常了。

那麼通信對方返回應答時間過長會引起的級聯故障

  • 線程問題:假如所有的可用線程都被故障服務器阻塞,那麼後續所有的IO消息都將被隊列中排隊
  • 隊列問題:如果隊列採用的是有界隊列隊列滿了之後那麼就會無法後續處理請求;如果採用的是無界隊列,那麼會有OOM風險。

NIO

NIO,官方叫法是new IO,因爲它相對於之前出的java.io包是新增的

但是之前老的IO庫都是阻塞的,New IO類庫目標就是爲了讓Java支持非阻塞IO,所有更多的人稱爲Non-Block IO

緩衝區Buffer

Buffer是一個對象,通常是ByteBuffer類型

任何時候操作NIO中的數據,都需要經過緩衝區

NIO庫裏,所有數據操作是用緩衝區處理的

  • 讀取數據時,是直接讀到緩衝區中(這裏並沒有直接讀到某個地方,而是都放到緩衝區中)
  • 寫入數據時,寫入到緩衝區

緩衝區實質上是一個數組,通常是一個字節數組ByteBuffer,自身還需要維護讀寫位置,可以用指針或者偏移量來實現。

除了ByteBuffer還有其他基本類型緩衝區

  • CharBuffer:字符緩衝區
  • ShortBuffer:短整型緩衝區
  • IntBuffer:整形緩衝區
  • LongBuffer:長整型緩衝區
  • DoubleBuffer:雙精度緩衝區

通常是用ByteBuffer

通道Channel

網絡數據通過Channel讀取和寫入

Channel通道和Stream流最大的區別在於:

  • Channel的數據流向是雙向
  • Stream的數據流向是單向

這就意味着:使用Channel,可以同時進行讀和寫,他是全雙工模型。(可以聯想到HTTP1.1 HTTP2.0 HTTP3.0 ``websocket

多路複用器Selector

Selector是NIO編程的基礎

Selector不斷輪詢註冊在其上的Channel

如果某個Channel發生讀寫事件,就代表這個Channel是就緒狀態,會被Selector輪詢出來。

然後根據SelectionKey可以獲取就緒Channel的集合,進行後續IO操作。

一個Selector可以輪詢多個Channel,JDK是基於epoll代替傳統的select,所以不受句柄fd的限制

意味着,一個線程負責Selector的輪詢千萬個客戶端

AIO

NIO2.0引入了新的異步通道的概念,並提供了異步文件通道異步套接字通道的實現

  • 通過java.util.concurrent.Future類來表示異步操作的結果
  • 在執行異步操作的時候傳入一個java.nio.channels

CompletionHandler接口的實現類作爲操作完成的回調

NIO2.0異步socket通道是真正的異步非阻塞IO

  • 同步socket channel:SocketServerChannel
  • 異步socket channel:AsynchronousServerSocketChannel

它不需要通過多路複用器(selector)對註冊到裏面的通過進行輪詢操作,就可以實現異步讀寫

AIO和NIO最大的區別在於:異步Socket Channel是被動執行對象

  • NIO需要我們把channel註冊到selector上進行順序掃描、輪詢
  • AIO則是通過Future類,實現回調方法:completedfailed

4種IO對比

IO模型主要是探討2個維度:

  • 同步/異步
  • 阻塞/非阻塞

同步/異步的判斷標準主要是:Channel的問題

阻塞/非阻塞的判斷標準主要是:selector的問題

阻塞的關鍵點在於:建立連接數據傳輸

BIO(阻塞)意味着在完成建立連接(accpet)動作之後,才能進行後續操作

NIO(非阻塞)在處理客戶端的連接時,可以將對應的channel註冊到Selector上,此時我不管他好了沒有,我有Selecotr來幫我去掃就緒態的channel,所以他是非阻塞的

異步非阻塞IO

異步非阻塞IO:AIO

有的人也叫JDK1.4推出的NIO爲異步非阻塞IO

但是嚴格來說,它只能被稱爲是非阻塞IO,並不是真正意義上的異步

前期selector的底層是通過select/poll來實現的,雖然是用epoll替代了select/poll,上層的API沒有變化,只是一次NIO的性能優化,仍舊沒有改變IO的模型

JDK1.7提供的NIO2.0新增了:異步套接字通道,他纔是真正的異步IO。

多路複用器Selector

Selector的核心功能:就是用來輪詢註冊在它上面的Channel

當發現某個就緒態的Channel,就會找出他的SelectionKey,然後進行後續的IO操作。

前期的時候JDK1.4,selector底層是基於select/poll技術實現

後面優化,使用epoll來代替

僞異步IO

只是在線程層面上進行了一次優化,IO模型並沒有改變

通過處理任務Task隊列+線程池處理請求的方式來優化資源

解決了BIO的線程和請求:1對1的關係

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章