做餅的網(wǎng)站/人民網(wǎng)疫情最新消息
一、什么是 I/O?
?I/O 描述了計算機系統(tǒng)與外部設(shè)備(磁盤)之間通信的過程。
為了保證操作系統(tǒng)的穩(wěn)定性和安全性,一個進程的地址空間劃分為?用戶空間(User space)?和?內(nèi)核空間(Kernel space )?。用戶進程(應用程序)想要執(zhí)行 IO 操作的話,必須通過?系統(tǒng)調(diào)用?來間接訪問內(nèi)核空間。
當應用程序發(fā)起 I/O 調(diào)用后,會經(jīng)歷兩個步驟:
- 內(nèi)核等待 I/O 設(shè)備準備好數(shù)據(jù)——階段①
- 內(nèi)核將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間——階段②
二、同步與異步、阻塞與非阻塞
阻塞與非阻塞是針對 線程 來說的;同步與異步是針對 整個I/O操作 來說的
同步阻塞IO(BIO):應用程序發(fā)起請求后,需等待階段①和②都完成,在整個IO期間不能做別的,必須等待整個IO操作完成才可進行下個任務。
同步非阻塞IO:應用程序發(fā)起請求后,在階段①不斷輪詢內(nèi)核數(shù)據(jù)是否準備好,在階段②需阻塞等待,在整個IO期間不能做別的,必須等待整個IO操作完成才可進行下個任務。
異步阻塞IO:應用程序發(fā)起請求后,立刻返回,在等待階段①和②期間也不做別的(因為阻塞掛起當前線程),就等著內(nèi)核通知。內(nèi)核完成①和②之后,發(fā)起回調(diào),應用程序可以直接處理數(shù)據(jù)。
異步非阻塞IO(AIO):應用程序發(fā)起請求后,立刻返回,在等待階段①和②期間做別的事。內(nèi)核完成①和②之后,發(fā)起回調(diào),應用程序可以直接處理數(shù)據(jù)。
簡述JAVA同步、異步、阻塞和非阻塞之間的區(qū)別_java_腳本之家 (jb51.net)
什么是阻塞和非阻塞?什么是同步和異步?什么是BIO、NIO、AIO? - 沙灘de流沙 - 博客園 (cnblogs.com)
三、?I/O 多路復用模型?(NIO)
同步非阻塞IO的應用程序不斷進行 I/O 系統(tǒng)調(diào)用輪詢數(shù)據(jù)是否已經(jīng)準備好的過程是十分消耗 CPU 資源的。
IO 多路復用模型,通過減少無效的系統(tǒng)調(diào)用,減少了對 CPU 資源的消耗。
IO 多路復用模型中,線程首先發(fā)起 select 調(diào)用(階段①),詢問內(nèi)核數(shù)據(jù)是否準備就緒(一個線程管理多個客戶端連接,也就是在這期間詢問每個連接),等內(nèi)核把數(shù)據(jù)準備好了(某一個連接),用戶線程再發(fā)起 read 調(diào)用(階段②)。read 調(diào)用的過程(數(shù)據(jù)從內(nèi)核空間->用戶空間)還是阻塞的。
四、深入理解
同步與異步(是誰通知消息)
同步:?發(fā)起一個調(diào)用后,被調(diào)用者未處理完請求之前,調(diào)用不返回。需要反復詢問數(shù)據(jù)是否就緒。
異步:?發(fā)起一個調(diào)用后,立刻得到被調(diào)用者的回應表示已接收到請求,但是被調(diào)用者并沒有返回結(jié)果,此時我們可以處理其他的請求,被調(diào)用者通常依靠事件,回調(diào)等機制來通知調(diào)用者其返回結(jié)果。
同步和異步的區(qū)別最大在于——異步是被調(diào)用者來通知調(diào)用者處理結(jié)果。同步需要調(diào)用者自己反復詢問處理結(jié)果。
阻塞和非阻塞(線程等待消息通知時的可不可以做別的事)
阻塞:?發(fā)起一個請求,調(diào)用者一直等待請求結(jié)果返回,無法從事其他任務,只有當條件就緒才能繼續(xù)。
非阻塞:?發(fā)起一個請求,調(diào)用者不用一直等著結(jié)果返回,可以先去干其他事情。
同步非阻塞 和 異步非阻塞 區(qū)別
- 同步阻塞 是自己發(fā)起之后,就一直等待處理結(jié)果。
- 同步非阻塞 是自己等待消息通知,需自己反復詢問處理結(jié)果。
- 異步非阻塞 是別人通過事件或回調(diào)機制來通知自己,自己不需要詢問。
BIO(Socket 網(wǎng)絡編程)
- 同步阻塞I/O
- 一請求一線程
- 面向流(Stream)。數(shù)據(jù)直接讀寫到 Stream 對象中。
- 不適合高并發(fā)場景
在 Java 虛擬機中,線程是寶貴的資源,線程的創(chuàng)建和銷毀成本很高,除此之外,線程的切換成本也是很高的。
痛點
- 處理多個客戶端請求,就必須使用多線程。如果這個連接不做任何事情的話就會造成不必要的線程開銷。線程開銷大。線程之間的切換也會浪費資源開銷。
- 如果并發(fā)訪問量增加會導致線程數(shù)急劇膨脹可能會導致線程堆棧溢出、創(chuàng)建新線程失敗等問題,最終導致進程宕機或者僵死,不能對外提供服務。
優(yōu)化(偽異步IO)
- 可以通過?線程池機制?改善。當有新的客戶端接入時,將客戶端的 Socket 封裝成一個Task(該任務實現(xiàn)java.lang.Runnable接口)投遞到后端的線程池中進行處理,JDK 的線程池維護一個消息隊列和 N 個活躍線程,對消息隊列中的任務進行處理。由于線程池可以設(shè)置消息隊列的大小和最大線程數(shù),因此,它的資源占用是可控的,無論多少個客戶端并發(fā)訪問,都不會導致資源的耗盡和宕機。
- 避免了為每個請求都創(chuàng)建一個獨立線程造成的線程資源耗盡問題。不過因為它的底層任然是同步阻塞的BIO模型,因此無法從根本上解決問題。
NIO(SocketChannel 網(wǎng)絡編程)
- I/O 多路復用模型
- 多通道一線程
- 面向緩沖區(qū)。所有數(shù)據(jù)讀寫到緩沖區(qū)。
- 三大核心組件:
- Buffer(緩沖區(qū))。在NIO厙中,所有數(shù)據(jù)都是用緩沖區(qū)處理的。在讀取數(shù)據(jù)時,它是直接讀到緩沖區(qū)中的; 在寫入數(shù)據(jù)時,寫入到緩沖區(qū)中。任何時候訪問NIO中的數(shù)據(jù),都是通過緩沖區(qū)進行操作。
- Channel(通道)。NIO 通過Channel(通道) 進行讀寫。通道是雙向的,可讀也可寫,而流的讀寫是單向的。無論讀寫,通道只能和Buffer交互。因為 Buffer,通道可以異步地讀寫。
- Selector(選擇器)。NIO的選擇器用于使用單個線程管理多個通道。只有在通道里真正有讀寫事件發(fā)生時(事件驅(qū)動),才會交給線程讀寫(Selector會一直詢問每個通道有沒有讀寫事件,這個過程是同步的),因此,它只需要較少的線程來處理這些通道。不必為每一個連接都創(chuàng)建一個線程,也不必去維護多個線程。避免了多個線程之間的上下文切換,導致資源的浪費。(只有網(wǎng)絡IO才會使用選擇器,文件IO是不需要使用的)
- 適合高并發(fā)場景
痛點
- NIO的類庫和API繁雜,學習成本高。
- 需要熟悉Java多線程編程。這是因為NIO編程涉及到Reactor模式,你必須對多線程和網(wǎng)絡編程非常熟悉,才能寫出高質(zhì)量的NIO程序。
-
JDK 的 NIO 底層由 epoll 實現(xiàn),該實現(xiàn)飽受詬病的空輪詢 bug 會導致 cpu 飆升 100%
當我們調(diào)用socket.read()、socket.write()這類阻塞函數(shù)的時候,這類函數(shù)不能立即返回,也無法中斷,需要等待socket可讀或者可寫,才會返回,因此一個線程只能處理一個請求。在這等待的過程中,cpu并不干活,(即阻塞住了),那么cpu的資源就沒有很好地利用起來。因此對于這種情況,我們使用多線程來提高cpu資源的利用率:在等待的這段時間,就可以切換到別的線程去處理事件,直到socket可讀或可寫了,通過中斷信號通知cpu,再切換回來繼續(xù)處理數(shù)據(jù)。例如線程A正在等待socket可讀,而線程B已經(jīng)就緒了,那么就可以先切換到線程B去處理。雖然上下文切換也會花一些時間,但是遠比阻塞在線程A這里空等要好。當然計算機內(nèi)部實際的情況比這復雜得多。
而NIO的讀寫函數(shù)可以立刻返回,這就給了我們不開線程利用CPU的最好機會:如果一個連接不能讀寫(socket.read()返回0或者socket.write()返回0),我們可以把這件事記下來。因此只需要一個Selector不斷地輪詢這些事件,一旦有就緒的時間,處理即可。不需要多線程。
AIO
- 異步非阻塞I/O
- 異步 IO 是基于事件和回調(diào)機制實現(xiàn)的,也就是應用操作之后會直接返回(發(fā)起請求后,線程直接返回干別的事,這個過程是異步的,只要等待操作完成后,通知該線程),不會堵塞在那里,當后臺處理完成,操作系統(tǒng)會通知相應的線程進行后續(xù)的操作。
痛點
- 復雜性:雖然AIO的編程模型相對簡單,但是由于其非阻塞的特性,編程復雜性可能會增加。例如,需要處理操作完成的通知,以及可能的并發(fā)問題。
- 資源消耗:AIO可能會消耗更多的系統(tǒng)資源。因為每個操作都需要創(chuàng)建一個回調(diào)函數(shù),如果并發(fā)連接數(shù)非常大,可能會消耗大量的系統(tǒng)資源。
- 可移植性:AIO在某些平臺上可能不可用或者性能不佳。因此,如果需要跨平臺的可移植性,可能需要考慮使用其他I/O模型。
BIO適合連接數(shù)目較少且固定的架構(gòu)。
NIO適合連接數(shù)目多,但是并發(fā)讀寫操作相對較少的場景。
AIO則適合連接數(shù)目多,且并發(fā)讀寫操作也多的場景。
五、思考題
看看你現(xiàn)在能回答這些問題了嗎?
- 簡單說下 BIO、NIO 和 AIO?具體使用、區(qū)別及原理?
- BIO,NIO,AIO的痛點,怎么優(yōu)化?
- 為什么BIO比NIO性能差?簡單講講區(qū)別?
- 假設(shè)有100個連接,采用NIO的方式要服務端要分配幾個線程,采用BIO的方式呢?
阿里畢玄-測試Java編程能力-我的回答(一)_java bio建立100個連接-CSDN博客
- 為啥要用異步IO不用多線程,不是一樣可以加速嗎?
- NIO的設(shè)計架構(gòu)?JDK中NIO有哪些重要組件?
- 同步、異步調(diào)用方式的具體實現(xiàn)