一文讓你了解透徹Java中的IO模型
什么是IO
IO(Input/Output),也就是輸入和輸出的簡稱,從計算機結(jié)構(gòu)的角度來看,IO,就是輸入數(shù)據(jù)到計算機中,計算機輸出數(shù)據(jù)到計算機外,下面有一張十分經(jīng)典的馮·諾伊曼結(jié)構(gòu)圖,將計算機分為五大部分:運算器、控制器、存儲器、輸入設(shè)置、輸出設(shè)備。
輸入設(shè)備向計算機輸入數(shù)據(jù),輸出設(shè)備接收計算機輸出的數(shù)據(jù)。
所以,從計算機結(jié)構(gòu)的角度來看IO,IO就是描述了計算機系統(tǒng)和外部設(shè)備之間通信的過程。
接下來我們再從應(yīng)用程序的角度去了解一下IO:在操作系統(tǒng)中,為了保證操作系統(tǒng)的穩(wěn)定性和安全性,一個進程的地址空間被劃分為了用戶空間(User space)和內(nèi)核空間(Kernel space) 。
那么什么是用戶空間,什么是內(nèi)核空間呢?
- 用戶空間是普通應(yīng)用程序可訪問的內(nèi)存區(qū)域。
- 內(nèi)核空間是操作系統(tǒng)內(nèi)核訪問的區(qū)域,獨立于普通的應(yīng)用程序,是受保護的內(nèi)存空間。
可以簡單的理解為我們平常運行的應(yīng)用程序,都是運行在用戶空間中的,它沒有權(quán)限去進行一些系統(tǒng)態(tài)級別的資源相關(guān)操作,比如文件管理、內(nèi)存管理、等等,這些操作都是要依賴內(nèi)核空間才能完成,也就是說,我們?nèi)绻胍M行IO操作,一定是要依賴內(nèi)核空間的能力,并且這里需要注意一點:用戶空間的程序不能直接訪問內(nèi)核空間。
那么如果要發(fā)起一次IO操作,那應(yīng)該怎么辦呢?
當(dāng)我們需要執(zhí)行一次IO操作的時候,由于沒有執(zhí)行IO操作的權(quán)限,只能發(fā)起系統(tǒng)調(diào)用請求操作系統(tǒng)來幫忙完成,所以用戶進程想要執(zhí)行IO操作的話,必須通過系統(tǒng)調(diào)用來間接的訪問內(nèi)核空間,比如我們最常見的IO操作就是磁盤IO(讀寫文件)和網(wǎng)絡(luò)IO(網(wǎng)絡(luò)請求和響應(yīng))。
那么從應(yīng)用程序的視角去看IO的話,我們的應(yīng)用程序發(fā)起對操作系統(tǒng)的內(nèi)核發(fā)起IO調(diào)用(系統(tǒng)調(diào)用,注意只是調(diào)用),操作系統(tǒng)去負責(zé)內(nèi)核執(zhí)行具體的IO操作。(如果這段話不是很好理解,可以理解為調(diào)包,比如別人寫好了的工具類,我們不需要關(guān)注里面的實現(xiàn)細節(jié),只需要調(diào)用它的方法就好了,這里我們的應(yīng)用程序就是發(fā)起了一個調(diào)用,但是真正執(zhí)行的還是我們的操作系統(tǒng))
當(dāng)應(yīng)用程序發(fā)起IO調(diào)用之后,會經(jīng)歷下面兩個步驟:(這里一定要記住這兩點,它和后面息息相關(guān)?。?/strong>
- 內(nèi)核等待IO設(shè)備準(zhǔn)備好數(shù)據(jù)
- 內(nèi)核將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間
常見的IO模型
IO模型有很多種(比如在UNIX系統(tǒng)下,IO模型一共分為5種:同步阻塞IO、同步非阻塞IO、IO多路復(fù)用、信號驅(qū)動IO、異步IO),本篇文章只講述Java中的IO模型,Java中的IO被分為三類:BIO、NIO、AIO。
BIO(Blocking IO)
BIO屬于同步阻塞IO模型,應(yīng)用程序發(fā)起read調(diào)用之后,會一直阻塞,直到內(nèi)核把數(shù)據(jù)從內(nèi)核空間拷貝至用戶空間。
BIO就是Java中最傳統(tǒng)的IO模型,相關(guān)的類和接口都在java.io這個包下面,因為現(xiàn)在用的人很少(后面看它的特點就知道為什么了),所以我們這里簡單介紹一下它的一些特點就好了。
- 對高并發(fā)場景下對于線程資源的消耗較高,每一個連接需要使用一條線程單獨處理。
- 傳輸較小對象時存在頻繁的線程上下文切換等性能問題。
如何優(yōu)化
那么怎么去優(yōu)化這個BIO呢?
首先上面說了BIO的缺點之一就是它是有多少連接就需要多少線程的模型,但是對于用戶來說,打開一個連接,然后關(guān)閉一個連接是十分常見且頻繁的事情,而與之對應(yīng)的就是創(chuàng)建線程和銷毀線程,但是創(chuàng)建線程和銷毀線程對于操作系統(tǒng)來說是十分消耗資源的,所以想到的優(yōu)化就是使用線程池去進行優(yōu)化BIO。
NIO的面世
但是BIO的模型決定了它的上限,它始終是同步阻塞的IO模型,阻塞就會導(dǎo)致不能使用單線程處理多個請求,所以這個時候就需要修改它的模型,將調(diào)用read()、write()方法不再是阻塞的,這樣就可以使用單線程處理多個請求,而這樣就不再是同步阻塞的IO模型了,也就是我們下面要說到的NIO了。
NIO(Non-blocking/New IO)
Java的NIO是Java1.4引入的,對應(yīng)的是java.nio包,提供了channel、selector、buffer等抽象。
Java中的NIO可以看作是IO多路復(fù)用模型,但是也有人認(rèn)為它是同步非阻塞IO模型,這里我們先簡單介紹一下這兩種模型:
同步非阻塞IO模型
這個相比同步阻塞IO模型,同步非阻塞IO模型就是通過輪詢的方式,避免了一直阻塞。
但是從圖中也能看出問題所在,就是應(yīng)用程序不斷的進行IO系統(tǒng)調(diào)用輪詢數(shù)據(jù)是否已經(jīng)準(zhǔn)備就緒的這段時間,是十分消耗CPU資源的(說白話就是反復(fù)調(diào)用read操作) 。
所以這個時候就引入了IO多路復(fù)用模型。
IO多路復(fù)用模型
在IO多路復(fù)用模型中,線程首先發(fā)起select調(diào)用,詢問內(nèi)核數(shù)據(jù)是否準(zhǔn)備就緒,等待內(nèi)核把數(shù)據(jù)準(zhǔn)備好了,會返回一個ready調(diào)用,告訴你,我準(zhǔn)備好了,這個時候用戶線程再發(fā)起read調(diào)用。注意:read調(diào)用的過程依然是阻塞的(數(shù)據(jù)從內(nèi)核空間拷貝至用戶空間這段時間) 。
IO多路復(fù)用模型通過無效的系統(tǒng)調(diào)用,減少了對CPU資源的消耗。
Java中的NIO
Java中的NIO,有一個十分重要的概念,就是選擇器(selector),也被稱為多路復(fù)用器,通過它就可以實現(xiàn)使用一個線程管理多個客戶端連接(這里是不是和BIO就不一樣了,最大的優(yōu)化的點就在這里) 。當(dāng)客戶端數(shù)據(jù)到了之后,線程再為客戶端進行服務(wù)。
下面給出一張JavaNIO的圖:
這張圖就能很好的說明了NIO的特點了。
AIO(Asynchronous IO)
AIO,也被稱為NIO 2.0,Java7中引入的異步IO模型,很多人都不理解非阻塞IO和異步IO到底有什么差別,其實異步IO就是基于事件和回調(diào)機制去實現(xiàn)的,也就是說用戶調(diào)用操作之后會立馬返回,不會阻塞,當(dāng)后臺處理完成,操作系統(tǒng)會通知相應(yīng)的線程進行后續(xù)操作。
目前來說AIO的應(yīng)用還沒有十分廣泛,應(yīng)用最多的是NIO。
總結(jié)
最后放一張圖來總結(jié)Java中的IO模型:
到此這篇關(guān)于一文讓你了解透徹Java中的IO模型的文章就介紹到這了,更多相關(guān)Java IO模型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
自定義JmsListenerContainerFactory時,containerFactory字段解讀
這篇文章主要介紹了自定義JmsListenerContainerFactory時,containerFactory字段解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07關(guān)于MyBatis中Mapper?XML熱加載優(yōu)化
大家好,本篇文章主要講的是關(guān)于MyBatis中Mapper?XML熱加載優(yōu)化,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下2022-01-01spring kafka框架中@KafkaListener 注解解讀和使用案例
Kafka 目前主要作為一個分布式的發(fā)布訂閱式的消息系統(tǒng)使用,也是目前最流行的消息隊列系統(tǒng)之一,這篇文章主要介紹了kafka @KafkaListener 注解解讀,需要的朋友可以參考下2023-02-02