通過Java帶你了解網(wǎng)絡(luò)IO模型
1.BIO
1.1 簡述
BIO是同步阻塞IO,所有連接都是同步執(zhí)行的,在上一個(gè)連接未處理完的時(shí)候是無法接收下一個(gè)連接

1.2 代碼示例
在上述代碼中,如果啟動(dòng)一個(gè)客戶端起連接服務(wù)端時(shí)如果沒有發(fā)送數(shù)據(jù),那么下一個(gè)連接將永遠(yuǎn)無法進(jìn)來
public static void main(String[] args) {
try {
// 監(jiān)聽端口
ServerSocket serverSocket = new ServerSocket(8080);
// 等待客戶端的連接過來,如果沒有連接過來,就會(huì)阻塞
while (true) {
// 阻塞IO中一個(gè)線程只能處理一個(gè)連接
Socket socket = serverSocket.accept();
System.out.println("客戶端建立連接:"+socket.getPort());
String line = null;
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
line = bufferedReader.readLine();
System.out.println("客戶端的數(shù)據(jù):" + line);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write("ok\n");
bufferedWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}1.3優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):
簡單易用,代碼實(shí)現(xiàn)比較簡單。
對(duì)于低并發(fā)量的場景,因?yàn)槊總€(gè)連接都有獨(dú)占的線程處理IO操作,因此可以保證每個(gè)連接的IO操作都能夠及時(shí)得到處理。
對(duì)于數(shù)據(jù)量較小的IO操作,同步阻塞IO模型的性能表現(xiàn)較好。
缺點(diǎn):
由于每一個(gè)客戶端連接都需要開啟一個(gè)線程,因此無法承載高并發(fā)的場景。
線程切換的開銷比較大,會(huì)導(dǎo)致系統(tǒng)性能下降。
對(duì)于IO操作較慢的情況下,會(huì)占用大量的線程資源,導(dǎo)致系統(tǒng)負(fù)載過高。
對(duì)于處理大量連接的服務(wù)器,BIO模型的性能較低,無法滿足需求。
1.4 思考
問:既然每個(gè)連接進(jìn)來都會(huì)阻塞,那么是否可以使用多線程的方式接收處理?
答:當(dāng)然可以,但是這樣如果有1w個(gè)連接那么就要啟動(dòng)1w個(gè)線程去處理嗎,線程是非常寶貴的資源,頻繁使用線程對(duì)系統(tǒng)的開銷是非常大的
2. NoBlockingIO
2.1 簡述
NoBlockingIO是同步非阻塞IO,相對(duì)比阻塞IO,他在接收數(shù)據(jù)的時(shí)候是非阻塞的,會(huì)一直輪詢去問內(nèi)核是否準(zhǔn)備好數(shù)據(jù),直到有數(shù)據(jù)返回
ps: NoBlockingIO并不是真正意義上的NIO

2.2 代碼示例
在下述代碼中,將BIO中的ServerSocket修改為ServerSocketChannel,然后configureBlocking為false則為非阻塞,從而數(shù)據(jù)都是在channel的buffer(緩沖區(qū))中獲取,不理解沒關(guān)系,就當(dāng)作是設(shè)置非阻塞IO的方式就好
此時(shí)在accept中是非阻塞的,不斷的等待客戶端進(jìn)來
注意
- accept是非阻塞,不斷輪詢,如果為空則跳過,不為空則添加連接
- 讀數(shù)據(jù)是非阻塞,不斷的輪詢連接,等待客戶端寫入數(shù)據(jù)
public static List<SocketChannel> channelList = new ArrayList<>();
public static void main(String[] args) {
try {
// 相當(dāng)于serverSocket
// 1.支持非阻塞 2.數(shù)據(jù)總是寫入buffer,讀取也是從buffer中去讀 3.可以同時(shí)讀寫
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 設(shè)置非阻塞
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
while (true){
// 這里將不再阻塞
SocketChannel socketChannel = serverSocketChannel.accept();
if(socketChannel != null){
socketChannel.configureBlocking(false);
channelList.add(socketChannel);
}else {
System.out.println("沒有請(qǐng)求過來?。?!");
}
for (SocketChannel client : channelList){
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 也不阻塞
int num = client.read(byteBuffer);
if(num>0){
System.out.println("客戶端端口:"+ client.socket().getPort()+",客戶端收據(jù):"+new String(byteBuffer.array()));
}else {
System.out.println("等待客戶端寫數(shù)據(jù)");
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}2.3 優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):
非阻塞I/O可以同時(shí)處理多個(gè)客戶端連接,提高服務(wù)器的并發(fā)處理能力。
由于非阻塞I/O的模式下,一個(gè)線程可以處理多個(gè)I/O操作,因此可以減少線程切換次數(shù),提高系統(tǒng)性能
缺點(diǎn):
- 有很多無效訪問,因?yàn)闆]有連接的時(shí)候accept也不會(huì)阻塞,很多為空的accpet
- 如果客戶端沒有寫數(shù)據(jù),會(huì)一直向內(nèi)核訪問,每次都是一個(gè)系統(tǒng)調(diào)用,非常浪費(fèi)系統(tǒng)資源
2.4 思考
問 :既然一直輪詢會(huì)產(chǎn)生很多的無效輪詢,并浪費(fèi)系統(tǒng)資源,那么有沒有更好的辦法呢
答: 通過事件注冊的方式(多路復(fù)用器)
3. NIO(NewIO)
3.1 簡述
NewIO才是真正意義上的NIO,NoBlockingIO只能算是NIO的前身,因?yàn)镹ewIO在NoBlockingIO上加上了多路復(fù)用器,使得NIO更加完美
在下圖中,channel不再是直接循環(huán)調(diào)用內(nèi)核,而是將連接,接收,讀取,寫入等事件注冊到多路復(fù)用器中,如果沒有事件到來將會(huì)阻塞等待

NIO三件套(記):
- channel: 介于字節(jié)緩沖區(qū)(buffer)和套接字(socket)之間,可以同時(shí)讀寫,支持異步IO
- buffer: 字節(jié)緩沖區(qū),是應(yīng)用程序和通道之間進(jìn)行IO數(shù)據(jù)傳輸?shù)闹修D(zhuǎn)
- selector:多路復(fù)用器,監(jiān)聽服務(wù)端和客戶端的管道上注冊的事件
3.2 代碼示例
從代碼示例可以看到,在沒有連接的時(shí)候會(huì)在selector.select()中阻塞,然后等待客戶端連接或者寫入數(shù)據(jù),不同的監(jiān)聽事件會(huì)有不同的處理方法
具體流程:
服務(wù)端創(chuàng)建Selector,并注冊O(shè)P_ACCEPT接受連接事件,然后調(diào)用select阻塞等待連接進(jìn)來
客戶端注冊O(shè)P_CONNECT事件,表示連接客戶端,連接成功后會(huì)調(diào)用handlerConnect方法
2.1 handlerConnect方法會(huì)注冊O(shè)P_READ事件并向服務(wù)端寫數(shù)據(jù)
這時(shí)候服務(wù)端會(huì)收到OP_ACCEPT后就會(huì)走到handlerAccept方法,表示接受連接
3.1handlerAccept方法也會(huì)注冊一個(gè)OP_READ事件并向客戶端寫數(shù)據(jù)
客戶端接收到服務(wù)端的數(shù)據(jù)后會(huì)再次喚醒select方法,然后判斷為isReadable(讀事件,服務(wù)端寫入給客戶端,那么客戶端就是讀),handlerRead方法將會(huì)把服務(wù)端寫入的數(shù)據(jù)讀取
反之亦然,服務(wù)端也會(huì)收到客戶端寫入的數(shù)據(jù),然后通過讀事件將數(shù)據(jù)讀取
服務(wù)端代碼
public class NewIOServer {
static Selector selector;
public static void main(String[] args) {
try {
selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
// 需要把serverSocketChannel注冊到多路復(fù)用器上
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 阻塞
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
handlerAccept(key);
} else if (key.isReadable()) {
handlerRead(key);
}else if(key.isWritable()){
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handlerRead(SelectionKey key) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer allocate = ByteBuffer.allocate(1024);
try {
socketChannel.read(allocate);
System.out.println("server msg:" + new String(allocate.array()));
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handlerAccept(SelectionKey key) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
// 不阻塞
try {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.write(ByteBuffer.wrap("It‘s server msg".getBytes()));
// 讀取客戶端的數(shù)據(jù)
socketChannel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
}客戶端代碼
public class NewIOClient {
static Selector selector;
public static void main(String[] args) {
try {
selector = Selector.open();
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("localhost", 8080));
// 需要把socketChannel注冊到多路復(fù)用器上
socketChannel.register(selector, SelectionKey.OP_CONNECT);
while (true) {
// 阻塞
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isConnectable()) {
handlerConnect(key);
} else if (key.isReadable()) {
handlerRead(key);
} else if (key.isWritable()) {
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handlerRead(SelectionKey key) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer allocate = ByteBuffer.allocate(1024);
try {
socketChannel.read(allocate);
System.out.println("client msg:" + new String(allocate.array()));
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handlerConnect(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
if (socketChannel.isConnectionPending()) {
socketChannel.finishConnect();
}
socketChannel.configureBlocking(false);
socketChannel.write(ByteBuffer.wrap("it‘s client msg".getBytes()));
socketChannel.register(selector,SelectionKey.OP_READ);
}
}3.3 優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):
NIO使用了非阻塞IO,可以大大提高系統(tǒng)的吞吐量和并發(fā)性能。
NIO提供了可擴(kuò)展的選擇器,可以監(jiān)控多個(gè)通道的狀態(tài),從而實(shí)現(xiàn)高效的事件驅(qū)動(dòng)模型。
NIO采用直接內(nèi)存緩沖區(qū),可以避免Java堆內(nèi)存的GC問題,提高內(nèi)存管理的效率。
缺點(diǎn):
- NIO的編程模型相比傳統(tǒng)的IO模型更加復(fù)雜,需要掌握較多的API和概念。
- NIO的實(shí)現(xiàn)難度較高,需要處理很多細(xì)節(jié)問題,如緩沖區(qū)的管理、選擇器的使用等。
- NIO的可靠性不如傳統(tǒng)的IO模型,容易出現(xiàn)空輪詢、系統(tǒng)負(fù)載過高等問題。
3.4 思考
問:select方法不是也阻塞嗎,那跟BIO有什么區(qū)別?
答:雖然他是在select阻塞,但是他通過事件注冊的方式,可以將多個(gè)selectKey同時(shí)加載到selectionKeys集合中,通過for循環(huán)處理不同的事件,而BIO只能由一個(gè)連接處理完才能處理下一個(gè)連接
問:什么是多路復(fù)用?
答:
多路:是指多個(gè)連接的管道,通道
復(fù)用:復(fù)用一個(gè)系統(tǒng)調(diào)用,原本多次系統(tǒng)調(diào)用變成一次
4. 擴(kuò)展select/poll、epoll
4.1 簡述
由第三部分的NIO可知,多路復(fù)用把for循環(huán)的系統(tǒng)調(diào)用變成了一次調(diào)用,那么他具體是怎么實(shí)現(xiàn)的?
其實(shí)我們仔細(xì)思考一下就能知道,他主要實(shí)現(xiàn)就是在selector.select(),由他去阻塞和觸發(fā)動(dòng)作。然而在實(shí)現(xiàn)這些功能的時(shí)候,就用到了三種模型,select、poll、epoll。因?yàn)閟elect和poll很相似,所以大家都會(huì)把他們歸為一類。
4.2 select/poll
我們先來說說什么是select?
實(shí)現(xiàn)過程
- 每一個(gè)socket調(diào)用select()方法后,socket的等待隊(duì)列就會(huì)放線程的引用,該線程就是你調(diào)用select的那個(gè)線程
- 當(dāng)其中一個(gè)socket發(fā)送數(shù)據(jù)的時(shí)候,他會(huì)將每一個(gè)socket在等待隊(duì)列中移除放入就緒隊(duì)列,這就表明一定有一個(gè)客戶端寫了數(shù)據(jù)過來,但是注意,這并不表示所有都有客戶端寫了數(shù)據(jù)過來
- 這時(shí)候喚醒主線程,然后去就緒隊(duì)列中遍歷找到客戶端寫的數(shù)據(jù)并返回
具體如下圖所示:

產(chǎn)生問題
- 因?yàn)閒d(file)是個(gè)數(shù)組,所以socket容量會(huì)有上限
- 只要有一個(gè)socket寫入就會(huì)遍歷所有socket,雖然減少了空輪詢問題,但是每次都要在所有socket中去找到已準(zhǔn)備好的那個(gè)socket需要消耗性能
什么是poll?
因?yàn)閒d是個(gè)數(shù)組,所以容量會(huì)達(dá)到上限,而poll則將這個(gè)數(shù)據(jù)結(jié)構(gòu)改成了鏈表,所以解決了select模型中上限的問題,但是遍歷socket的問題還是存在
select和poll的本質(zhì)區(qū)別就是一個(gè)是用數(shù)組存放socket,一個(gè)是用鏈表存放,其他地方?jīng)]有任何區(qū)別
4.3 epoll
epoll和select/poll相比,采用了事件回調(diào)的機(jī)制,并且使用紅黑樹去維護(hù)注冊的socket,如下圖所示

實(shí)現(xiàn)過程:
- 調(diào)用Selector.open的時(shí)候會(huì)創(chuàng)建一個(gè)eventpoll的文件,里面主要含有等待隊(duì)列,rbr(紅黑樹),就緒列表
- 然后在建立連接的時(shí)候調(diào)用epoll_ctl函數(shù)將socket放入epitem中
- 調(diào)用epoll_wait函數(shù)將線程放入等待隊(duì)列中,等待數(shù)據(jù)過來時(shí)喚醒
- 有數(shù)據(jù)寫入的時(shí)候會(huì)觸發(fā)epitem的回調(diào)方法,將該epitem移除并加入rdlist就緒列表中
- 當(dāng)有數(shù)據(jù)在就緒列表的時(shí)候,就會(huì)喚醒等待對(duì)列中的線程并處理數(shù)據(jù)
這樣通過紅黑樹來維護(hù)連接和通過就緒列表來處理數(shù)據(jù)就可以保證可以存放最大限度的socket數(shù)量,并且在喚醒線程處理去處理就緒列表的時(shí)候肯定都是需要處理并且已就緒的socket。完美的解決了select/poll中的問題
總結(jié)
epoll相較于select/poll的優(yōu)勢:
采用了事件驅(qū)動(dòng)的方式,可以處理大量的連接,效率更高。
支持邊緣觸發(fā)(ET)和水平觸發(fā)(LT)兩種模式,可以更靈活地處理IO事件。
記錄了上次處理的位置,可以避免重復(fù)的遍歷,更加高效。
高效利用了內(nèi)核空間和用戶空間的交互,避免了復(fù)制文件描述符。
4.4 擴(kuò)展話題
對(duì)于epoll的一些擴(kuò)展,有興趣的可以了解下,不感興趣可以略過
4.4.1 什么是ET和LT?
ET和LT是epoll工作模式中的兩種觸發(fā)方式,分別表示邊緣觸發(fā)(Edge Triggered)和水平觸發(fā)(Level Triggered)。
邊緣觸發(fā)(ET)
在ET模式下,當(dāng)一個(gè)文件描述符上出現(xiàn)事件時(shí),epoll_wait函數(shù)只會(huì)通知一次,即只有在文件描述符狀態(tài)發(fā)生變化時(shí)才會(huì)返回。如果應(yīng)用程序沒有處理完這個(gè)事件,那么下一次調(diào)用epoll_wait函數(shù)時(shí),它不會(huì)再返回這個(gè)事件,直到下一次狀態(tài)變化。
ET模式下的事件處理更為高效,因?yàn)樗粫?huì)在必要的時(shí)候通知應(yīng)用程序,避免了重復(fù)通知的問題。但是,由于ET模式只在狀態(tài)變化時(shí)通知一次,因此應(yīng)用程序需要及時(shí)處理事件,否則可能會(huì)錯(cuò)過某些事件。
水平觸發(fā)(LT)
在LT模式下,當(dāng)一個(gè)文件描述符上出現(xiàn)事件時(shí),epoll_wait函數(shù)會(huì)重復(fù)通知應(yīng)用程序,直到該文件描述符上的事件被處理完畢為止。如果應(yīng)用程序沒有處理完這個(gè)事件,那么下一次調(diào)用epoll_wait函數(shù)時(shí),它會(huì)再次返回這個(gè)事件,直到應(yīng)用程序處理完為止。
LT模式下的事件處理比較簡單,因?yàn)樗鼤?huì)重復(fù)通知應(yīng)用程序,直到應(yīng)用程序處理完為止。但是,由于重復(fù)通知的問題,LT模式下可能會(huì)導(dǎo)致一些性能問題。同時(shí),在LT模式下,應(yīng)用程序需要及時(shí)處理事件,否則可能會(huì)導(dǎo)致文件描述符上的事件積壓,影響系統(tǒng)的性能。
4.4.2 什么是驚群?
epoll的驚群(Thundering Herd)指的是多個(gè)線程或進(jìn)程同時(shí)等待同一個(gè)epoll文件描述符上的事件,
當(dāng)文件描述符上出現(xiàn)事件時(shí),內(nèi)核會(huì)通知所有等待的線程或進(jìn)程,但只有一個(gè)線程或進(jìn)程能夠真正處理該事件,其他線程或進(jìn)程會(huì)被喚醒但不能處理該事件,從而造成資源浪費(fèi)和性能降低的問題。
驚群問題是由于內(nèi)核通知等待線程或進(jìn)程的方式引起的。在epoll中,當(dāng)文件描述符上出現(xiàn)事件時(shí),內(nèi)核會(huì)通知所有等待的線程或進(jìn)程,而不是通知一個(gè)線程或進(jìn)程。因此,如果有多個(gè)線程或進(jìn)程等待同一個(gè)文件描述符,那么當(dāng)該文件描述符上出現(xiàn)事件時(shí),內(nèi)核會(huì)通知所有等待的線程或進(jìn)程,導(dǎo)致驚群問題。
為了解決驚群問題,可以采用以下兩種方式:
- 使用邊緣觸發(fā)(ET)模式:在ET模式下,當(dāng)文件描述符上出現(xiàn)事件時(shí),內(nèi)核只會(huì)通知一個(gè)等待的線程或進(jìn)程,從而避免了驚群問題。
- 采用互斥量或條件變量等機(jī)制:在多個(gè)線程或進(jìn)程等待同一個(gè)文件描述符時(shí),可以使用互斥量或條件變量等機(jī)制來控制線程或進(jìn)程的喚醒,從而避免驚群問題。
5. AIO
5.1簡述
在上面將的BIO,NIO中都是同步IO,BIO叫做同步阻塞,NIO叫做同步非阻塞,那么AIO則是異步IO,全名(Asynchronous I/O)

5.2 代碼示例
從代碼示例可以看到,以下代碼都是基于回調(diào)機(jī)制實(shí)現(xiàn)的,并不會(huì)像BIO和NIO一樣使用輪詢的方式,他不需要像同步IO一樣需要查找就緒socket,只要客戶端有數(shù)據(jù)寫入就會(huì)回調(diào)給服務(wù)端,既然是異步的所以就不會(huì)存在阻塞
服務(wù)端代碼
public class AIOServer {
public static void main(String[] args) throws Exception {
// 創(chuàng)建一個(gè)SocketChannel并綁定了8080端口
final AsynchronousServerSocketChannel serverChannel =
AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8080));
serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
try {
// 打印線程的名字
System.out.println("2--"+Thread.currentThread().getName());
System.out.println(socketChannel.getRemoteAddress());
ByteBuffer buffer = ByteBuffer.allocate(1024);
// socketChannel異步的讀取數(shù)據(jù)到buffer中
socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer buffer) {
// 打印線程的名字
System.out.println("3--"+Thread.currentThread().getName());
buffer.flip();
System.out.println(new String(buffer.array(), 0, result));
socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes()));
}
@Override
public void failed(Throwable exc, ByteBuffer buffer) {
exc.printStackTrace();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, Object attachment) {
exc.printStackTrace();
}
});
System.out.println("1--"+Thread.currentThread().getName());
Thread.sleep(Integer.MAX_VALUE);
}
}客戶端代碼
public class AIOClient {
private final AsynchronousSocketChannel client;
public AIOClient() throws IOException {
client = AsynchronousSocketChannel.open();
}
public static void main(String[] args) throws Exception {
new AIOClient().connect("localhost",8080);
}
public void connect(String host, int port) throws Exception {
// 客戶端向服務(wù)端發(fā)起連接
client.connect(new InetSocketAddress(host, port), null, new CompletionHandler<Void, Object>() {
@Override
public void completed(Void result, Object attachment) {
try {
client.write(ByteBuffer.wrap("這是一條測試數(shù)據(jù)".getBytes())).get();
System.out.println("已發(fā)送到服務(wù)端");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, Object attachment) {
exc.printStackTrace();
}
});
final ByteBuffer bb = ByteBuffer.allocate(1024);
// 客戶端接收服務(wù)端的數(shù)據(jù),獲取的數(shù)據(jù)寫入到bb中
client.read(bb, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
// 服務(wù)端返回?cái)?shù)據(jù)的長度result
System.out.println("I/O操作完成:" + result);
System.out.println("獲取反饋結(jié)果:" + new String(bb.array()));
}
@Override
public void failed(Throwable exc, Object attachment) {
exc.printStackTrace();
}
});
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}5.3 優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)勢:
更加高效:AIO采用回調(diào)方式,可以避免輪詢等操作對(duì)CPU的占用,減少CPU的負(fù)擔(dān),從而提高了系統(tǒng)的性能。
可以更好地利用系統(tǒng)資源:AIO能夠在I/O操作完成之前把線程釋放出來,可以更好地利用系統(tǒng)資源,提高系統(tǒng)的并發(fā)處理能力。
適用于高并發(fā)場景:AIO適用于高并發(fā)場景,能夠支持大量的并發(fā)連接,提高系統(tǒng)的處理能力。
缺點(diǎn):
學(xué)習(xí)成本高:相比于NIO,AIO的編程模型更加復(fù)雜,需要學(xué)習(xí)更多的知識(shí),學(xué)習(xí)成本更高。
實(shí)現(xiàn)難度大:AIO的實(shí)現(xiàn)難度比較大,需要對(duì)操作系統(tǒng)的底層機(jī)制有深入的了解,因此開發(fā)成本較高。
并非所有操作系統(tǒng)都支持:AIO并非所有操作系統(tǒng)都支持,只有Linux 2.6以上的內(nèi)核才支持AIO,因此跨平臺(tái)的支持較差。
ps: 說白了AIO很好用,但是太復(fù)雜
以上就是通過Java帶你了解網(wǎng)絡(luò)IO模型的詳細(xì)內(nèi)容,更多關(guān)于Java 網(wǎng)絡(luò)IO模型的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于TreeMap自定義排序規(guī)則的兩種方式
這篇文章主要介紹了關(guān)于TreeMap自定義排序規(guī)則的兩種方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之無權(quán)無向圖
這篇文章主要為大家介紹了Java數(shù)據(jù)結(jié)構(gòu)和算法之無權(quán)無向圖?,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-01-01
Java中關(guān)于String StringBuffer StringBuilder特性深度解析
這篇文章主要介紹了Java中關(guān)于String StringBuffer StringBuilder特性深度解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09
mybatis-puls中的resultMap數(shù)據(jù)映射
這篇文章主要介紹了mybatis-puls中的resultMap數(shù)據(jù)映射,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Serializable接口的作用_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了java中Serializable接口的作用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
SpringBoot項(xiàng)目優(yōu)雅的全局異常處理方式(全網(wǎng)最新)
這篇文章主要介紹了SpringBoot項(xiàng)目優(yōu)雅的全局異常處理方式(全網(wǎng)最新),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04

