Ⅰ java中BIO,NIO和AIO的區別和應用場景
Reactor and Proactor
IO讀寫時,多路復用機制都會依賴對一個事件多路分離器,負責把源事件的IO 事件分離出來,分別到相應的read/write事件分離器。涉及到事件分離器的兩種模式分別就是 Reactor和Proactor,Reactor是基於同步IO的,Proactor是基於非同步IO的。
在Reactor模式中,事件分離者等待某個事件或者可應用或個操作的狀態發生(比如文件描述符可讀寫,或者是socket可讀寫),事件分離者就把這個事件傳給事先注冊的事件處理函數或者回調函數,由後者來做實際的讀寫操作。
Ⅱ java的API中有哪些常用的包
Application Programming Interface 應用程序編程介面,Java的api就多的數不清了,平時編程用的都是API。
Ⅲ java中bio nio aio的區別和聯系
BIO是一個連接一個線程。
NIO是一個請求一個線程。
AIO是一個有效請求一個線程。
先來個例子理解一下概念,以銀行取款為例:
同步 : 自己親自出馬持銀行卡到銀行取錢(使用同步IO時,Java自己處理IO讀寫);
非同步 : 委託一小弟拿銀行卡到銀行取錢,然後給你(使用非同步IO時,Java將IO讀寫委託給OS處理,需要將數據緩沖區地址和大小傳給OS(銀行卡和密碼),OS需要支持非同步IO操作API);
阻塞 : ATM排隊取款,你只能等待(使用阻塞IO時,Java調用會一直阻塞到讀寫完成才返回);
非阻塞 : 櫃台取款,取個號,然後坐在椅子上做其它事,等號廣播會通知你辦理,沒到號你就不能去,你可以不斷問大堂經理排到了沒有,大堂經理如果說還沒到你就不能去(使用非阻塞IO時,如果不能讀寫Java調用會馬上返回,當IO事件分發器會通知可讀寫時再繼續進行讀寫,不斷循環直到讀寫完成)
Java對BIO、NIO、AIO的支持:
Java BIO : 同步並阻塞,伺服器實現模式為一個連接一個線程,即客戶端有連接請求時伺服器端就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,當然可以通過線程池機制改善。
Java NIO : 同步非阻塞,伺服器實現模式為一個請求一個線程,即客戶端發送的連接請求都會注冊到多路復用器上,多路復用器輪詢到連接有I/O請求時才啟動一個線程進行處理。
Java AIO(NIO.2) : 非同步非阻塞,伺服器實現模式為一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知伺服器應用去啟動線程進行處理,
Ⅳ java Nio讀寫為什麼是雙向
作者:美團技術團隊
鏈接:https://zhuanlan.hu.com/p/23488863
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請註明出處。
NIO(Non-blocking I/O,在Java領域,也稱為New I/O),是一種同步非阻塞的I/O模型,也是I/O多路復用的基礎,已經被越來越多地應用到大型應用伺服器,成為解決高並發與大量連接、I/O處理問題的有效方式。
那麼NIO的本質是什麼樣的呢?它是怎樣與事件模型結合來解放線程、提高系統吞吐的呢?
本文會從傳統的阻塞I/O和線程池模型面臨的問題講起,然後對比幾種常見I/O模型,一步步分析NIO怎麼利用事件模型處理I/O,解決線程池瓶頸處理海量連接,包括利用面向事件的方式編寫服務端/客戶端程序。最後延展到一些高級主題,如Reactor與Proactor模型的對比、Selector的喚醒、Buffer的選擇等。
註:本文的代碼都是偽代碼,主要是為了示意,不可用於生產環境。
傳統BIO模型分析
讓我們先回憶一下傳統的伺服器端同步阻塞I/O處理(也就是BIO,Blocking I/O)的經典編程模型:
{
ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//線程池
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(8088);
while(!Thread.currentThread.isInturrupted()){//主線程死循環等待新連接到來
Socket socket = serverSocket.accept();
executor.submit(new ConnectIOnHandler(socket));//為新的連接創建新的線程
}
class ConnectIOnHandler extends Thread{
private Socket socket;
public ConnectIOnHandler(Socket socket){
this.socket = socket;
}
public void run(){
while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){死循環處理讀寫事件
String someThing = socket.read()....//讀取數據
if(someThing!=null){
......//處理數據
socket.write()....//寫數據
}
}
}
}
這是一個經典的每連接每線程的模型,之所以使用多線程,主要原因在於socket.accept()、socket.read()、socket.write()三個主要函數都是同步阻塞的,當一個連接在處理I/O的時候,系統是阻塞的,如果是單線程的話必然就掛死在那裡;但CPU是被釋放出來的,開啟多線程,就可以讓CPU去處理更多的事情。其實這也是所有使用多線程的本質:
利用多核。
當I/O阻塞系統,但CPU空閑的時候,可以利用多線程使用CPU資源。
現在的多線程一般都使用線程池,可以讓線程的創建和回收成本相對較低。在活動連接數不是特別高(小於單機1000)的情況下,這種模型是比較不錯的,可以讓每一個連接專注於自己的I/O並且編程模型簡單,也不用過多考慮系統的過載、限流等問題。線程池本身就是一個天然的漏斗,可以緩沖一些系統處理不了的連接或請求。
不過,這個模型最本質的問題在於,嚴重依賴於線程。但線程是很"貴"的資源,主要表現在:
線程的創建和銷毀成本很高,在Linux這樣的操作系統中,線程本質上就是一個進程。創建和銷毀都是重量級的系統函數。
線程本身佔用較大內存,像Java的線程棧,一般至少分配512K~1M的空間,如果系統中的線程數過千,恐怕整個JVM的內存都會被吃掉一半。
線程的切換成本是很高的。操作系統發生線程切換的時候,需要保留線程的上下文,然後執行系統調用。如果線程數過高,可能執行線程切換的時間甚至會大於線程執行的時間,這時候帶來的表現往往是系統load偏高、CPU sy使用率特別高(超過20%以上),導致系統幾乎陷入不可用的狀態。
容易造成鋸齒狀的系統負載。因為系統負載是用活動線程數或CPU核心數,一旦線程數量高但外部網路環境不是很穩定,就很容易造成大量請求的結果同時返回,激活大量阻塞線程從而使系統負載壓力過大。
所以,當面對十萬甚至百萬級連接的時候,傳統的BIO模型是無能為力的。隨著移動端應用的興起和各種網路游戲的盛行,百萬級長連接日趨普遍,此時,必然需要一種更高效的I/O處理模型。
NIO是怎麼工作的
很多剛接觸NIO的人,第一眼看到的就是Java相對晦澀的API,比如:Channel,Selector,Socket什麼的;然後就是一坨上百行的代碼來演示NIO的服務端Demo……瞬間頭大有沒有?
我們不管這些,拋開現象看本質,先分析下NIO是怎麼工作的。
常見I/O模型對比
所有的系統I/O都分為兩個階段:等待就緒和操作。舉例來說,讀函數,分為等待系統可讀和真正的讀;同理,寫函數分為等待網卡可以寫和真正的寫。
需要說明的是等待就緒的阻塞是不使用CPU的,是在「空等」;而真正的讀寫操作的阻塞是使用CPU的,真正在"幹活",而且這個過程非常快,屬於memory ,帶寬通常在1GB/s級別以上,可以理解為基本不耗時。
下圖是幾種常見I/O模型的對比:
密碼:380p以上都是小編收集了大神的靈葯,喜歡的拿走吧!喜歡小編就輕輕關注一下吧!
Ⅳ JAVA NIO 和 AIO 的區別
Java NIO : 同步非阻塞,伺服器實現模式為一個請求一個線程,即客戶端發送的連接請求都會注冊到多路復用器上,多路復用器輪詢到連接有I/O請求時才啟動一個線程進行處理。
Java AIO(NIO.2) : 非同步非阻塞,伺服器實現模式為一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知伺服器應用去啟動線程進行處理,
NIO方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天伺服器,並發局限於應用中,編程比較復雜,JDK1.4開始支持。
AIO方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊伺服器,充分調用OS參與並發操作,編程比較復雜,JDK7開始支持
I/O屬於底層操作,需要操作系統支持,並發也需要操作系統的支持,所以性能方面不同操作系統差異會比較明顯。另外NIO的非阻塞,需要一直輪詢,也是一個比較耗資源的。所以出現AIO
Ⅵ 如果你是一個 Java 面試官,你會問哪些問題
1、談談你對 Java 平台的理解?「Java 是解釋執行」,這句話正確嗎?考點分析:對於這類籠統的問題,你需要盡量表現出自己的思維深入並系統化,Java 知識理解得也比較全面,一定要避免讓面試官覺得你是個「知其然不知其所以然」的人。畢竟明白基本組成和機制,是日常工作中進行問題診斷或者性能調優等很多事情的基礎,相信沒有招聘方會不喜歡「熱愛學習和思考」的面試者。回歸正題,對於 Java 平台的理解,可以從很多方面簡明扼要地談一下,例如:Java 語言特性,包括泛型、Lambda 等語言特性;基礎類庫,包括集合、IO/NIO、網路、並發、安全等基礎類庫。對於我們日常工作應用較多的類庫,面試前可以系統化總結一下,有助於臨場發揮。2、對比Hashtable、HashMap、TreeMap有什麼不同?考點分析:上面的回答,只是對一些基本特徵的簡單總結,針對Map相關可以擴展的問題很多,從各種數據結構、典型應用場景,到程序設計實現的技術考量,尤其是在Java 8里,HashMap本身發生了非常大的變化,這些都是經常考察的方面。很多朋友向我反饋,面試官似乎鍾愛考察HashMap的設計和實現細節,所以今天我會增加相應的源碼解讀,主要專注於下面幾個方面:理解Map相關類似整體結構,尤其是有序數據結構的一些要點。從源碼去分析HashMap的設計和實現要點,理解容量、負載因子等,為什麼需要這些參數,如何影響Map的性能,實踐中如何取捨等。理解樹化改造的相關原理和改進原因。除了典型的代碼分析,還有一些有意思的並發相關問題也經常會被提到,如HashMap在並發環境可能出現無限循環佔用CPU、size不準確等詭異的問題。我認為這是一種典型的使用錯誤,因為HashMap明確聲明不是線程安全的數據結構,如果忽略這一點,簡單用在多線程場景里,難免會出現問題。理解導致這種錯誤的原因,也是深入理解並發程序運行的好辦法。對於具體發生了什麼,你可以參考這篇很久以前的分析,裡面甚至提供了示意圖,我就不再重復別人寫好的內容了。3、Java 提供了哪些 IO 方式? NIO 如何實現多路復用?考點分析:在實際面試中,從傳統 IO 到 NIO、NIO 2,其中有很多地方可以擴展開來,考察點涉及方方面面,比如:基礎 API 功能與設計, InputStream/
Ⅶ java nio多路復用是什麼意思
就是NIO庫可以利用Selector多路復用各個Socket連接。
提高連接效率,降低連接的阻塞。
Ⅷ java網路io模型有幾種
#BIO---Blocking IO
- 每個socket一個線程,讀寫時線程處於阻塞狀態。
優點:實現簡單
缺點:無法滿足高並發,高接入的需求
- 不使用線程池的BIO模型,除了無法滿足高並發需求外,由於需要為每個請求創建一個線程,還可能因為接入大量不活躍連接而耗盡伺服器資源。
- 使用線程池的BIO模型,雖然控制了線程數量,但由於其本質上讀寫仍是阻塞的,仍無法滿足高並發需求。
#NIO---Non-Blocking IO(非阻塞IO)
##非阻塞IO和多路復用
非阻塞IO和多路復用實際上是兩個不用的概念,由於兩者通常結合在一起使用,因此兩者往往被混為一談。下面我將試著分清這兩個概念:
###非阻塞IO
與BIO相對應,非阻塞IO的讀寫方法無論是否有數據都立即返回,因此可以通過輪詢方式來實現,但輪詢方式的效率並不比BIO有顯著提高,因為每個連接仍然需要佔用一個線程。下面是輪詢方式實現的IO模式圖:
###多路復用
- 多路復用結合非阻塞IO能夠明顯提高IO的效率,這也是Java1.4把非阻塞IO和多路復用同時發布的原因。
- 多路復用的核心是多路復用器(Selector),它是需要操作系統底層支持的,簡單的說,就是進程把多個socket和它們關心的事件(比如連接請求或數據已准備好)都注冊在多路復用器上,操作系統會在事件發生時通知多路復用器,這樣進程就可以通過多路復用器知道在那個socket上發生了什麼時間,從而進行對應的處理。
- 多路復用的優點在於只需要一個線程監測(阻塞或輪詢方式均可)多路選擇器的狀態,只有在有事件需要發生時才會真正的創建線程進行處理,因此更適合高並發多接入的應用環境。
- 在Linux系統下,多路復用的底層實現是epoll方法,與select/poll的順序掃描不同,epoll採用效率更高的事件驅動方式,而且epoll方式並沒有socket個數限制。
##BIO和NIO的比較
- BIO適用於連接長期保持的應用,比如一個復雜系統中模塊之間通過長連接來進行通信。
- NIO加多路復用的模式更適合短連接、高並發、多接入的情形,比如網路伺服器。
##NIO網路編程的常用介面
##Reactor模式
Reactor模式用於解決事件分發處理的問題,Handler把自己的channel和關注的事件注冊到Selector中,當對應的事件發生在自己的channel上時,對應的handler就會得到通知並進行處理。
- 單線程的Reactor
消息的分發、讀寫、處理都在一個線程中處理,是Reactor最簡單的實現方式,如果消息的處理需要較長時間,會影響效率。
```java
//Reactor類,負責分發事件並調用對應的handler
class Reactor implements Runnable {
final Selector selector;
final ServerSocketChannel serverSocket;
//Reactor初始化
Reactor(int port) throws IOException {
selector = Selector.open();
serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(port));
serverSocket.configureBlocking(false); //必須配置為非阻塞
//Acceptor會在Reactor初始化時就注冊到Selector中,用於接受connect請求
SelectionKey sk = serverSocket.register(selector, SelectionKey.OP_ACCEPT);
sk.attach(new Acceptor()); //attach callback object, Acceptor
}
//分發消息並調用對應的handler
public void run() {
try {
while (!Thread.interrupted()) {
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
while (it.hasNext())
dispatch((SelectionKey)(it.next()); //Reactor負責dispatch收到的事件
selected.clear();
}
} catch (IOException ex) { /* ... */ }
}
void dispatch(SelectionKey k) {
Runnable r = (Runnable)(k.attachment()); //調用之前注冊的callback對象
if (r != null)
r.run();
}
//Acceptor也是一個handler,負責創建socket並把新建的socket也注冊到selector中
class Acceptor implements Runnable { // inner
public void run() {
try {
SocketChannel c = serverSocket.accept();
if (c != null)
new Handler(selector, c);
}
catch(IOException ex) { /* ... */ }
}
}
}
//Concrete Handler:用於收發和處理消息。
//在當前的實現中,使用Runnable介面作為每個具體Handler的統一介面
//如果在處理時需要參數和返回值,也可以為Handler另外聲明一個統一介面來代替Runnable介面
final class Handler implements Runnable {
final SocketChannel socket;
final SelectionKey sk;
ByteBuffer input = ByteBuffer.allocate(MAXIN);
ByteBuffer output = ByteBuffer.allocate(MAXOUT);
static final int READING = 0, SENDING = 1;
int state = READING;
Handler(Selector sel, SocketChannel c) throws IOException {
socket = c; c.configureBlocking(false);
// Optionally try first read now
sk = socket.register(sel, 0);
sk.attach(this); //將Handler作為callback對象
sk.interestOps(SelectionKey.OP_READ); //第二步,接收Read事件
sel.wakeup();
}
boolean inputIsComplete() { /* ... */ }
boolean outputIsComplete() { /* ... */ }
void process() { /* ... */ }
public void run() {
try {
if (state == READING) read();
else if (state == SENDING) send();
} catch (IOException ex) { /* ... */ }
}
void read() throws IOException {
socket.read(input);
if (inputIsComplete()) {
process();
state = SENDING;
// Normally also do first write now
sk.interestOps(SelectionKey.OP_WRITE); //第三步,接收write事件
}
}
void send() throws IOException {
socket.write(output);
if (outputIsComplete()) sk.cancel(); //write完就結束了, 關閉select key
}
}
//上面 的實現用Handler來同時處理Read和Write事件, 所以裡面出現狀態判斷
//我們可以用State-Object pattern來更優雅的實現
class Handler { // ...
public void run() { // initial state is reader
socket.read(input);
if (inputIsComplete()) {
process();
sk.attach(new Sender()); //狀態遷移, Read後變成write, 用Sender作為新的callback對象
sk.interest(SelectionKey.OP_WRITE);
sk.selector().wakeup();
}
}
class Sender implements Runnable {
public void run(){ // ...
socket.write(output);
if (outputIsComplete()) sk.cancel();
}
}
}
```
- 多線程Reacotr
處理消息過程放在其他線程中執行
```java
class Handler implements Runnable {
// uses util.concurrent thread pool
static PooledExecutor pool = new PooledExecutor(...);
static final int PROCESSING = 3;
// ...
synchronized void read() { // ...
socket.read(input);
if (inputIsComplete()) {
state = PROCESSING;
pool.execute(new Processer()); //使用線程pool非同步執行
}
}
synchronized void processAndHandOff() {
process();
state = SENDING; // or rebind attachment
sk.interest(SelectionKey.OP_WRITE); //process完,開始等待write事件
}
class Processer implements Runnable {
public void run() { processAndHandOff(); }
}
}
```
- 使用多個selector
mainReactor只負責處理accept並創建socket,多個subReactor負責處理讀寫請求
```java
Selector[] selectors; //subReactors集合, 一個selector代表一個subReactor
int next = 0;
class Acceptor { // ...
public synchronized void run() { ...
Socket connection = serverSocket.accept(); //主selector負責accept
if (connection != null)
new Handler(selectors[next], connection); //選個subReactor去負責接收到的connection
if (++next == selectors.length) next = 0;
}
}
```
#AIO
AIO是真正的非同步IO,它於JDK1.7時引入,它和NIO的區別在於:
- NIO仍然需要一個線程阻塞在select方法上,AIO則不需要
- NIO得到數據准備好的消息以後,仍然需要自己把消息復制到用戶空間,AIO則是通過操作系統的支持把數據非同步復制到用戶空間以後再給應用進程發出信號。
Ⅸ java.nio.channels的多路復用
非阻塞 I/O
描述
SelectableChannel 可實現多路復用的通道
DatagramChannel java .net.DatagramSocket 通道
Pipe.SinkChannel 對管道的寫入結束
Pipe.SourceChannel 對管道的讀取結束
ServerSocketChannel java .net.ServerSocket 通道
SocketChannel java .net.Socket 通道
Selector 可選擇通道的多路復用器
SelectionKey 表示通道注冊到選擇器的標記
Pipe 形成單向管道的兩個通道
多路復用的非阻塞 I/O 比面向線程的阻塞 I/O 的可伸縮性更好,由選擇器、可選擇通道 和選擇鍵 提供。
選擇器 是可選擇通道 的多路復用器,它是可被置於非阻塞模式 的特殊類型的通道。要執行多路復用的 I/O 操作,首先要創建一個或多個可選擇通道、將其置於非阻塞模式,並將注冊 到選擇器注冊一個通道會指定一組由選擇器測試其是否准備就緒的 I/O 操作,並返回一個表示該注冊的選擇鍵。
一旦已經向選擇器注冊了通道,就可執行選擇操作 以發現哪些通道(如果有)已經准備好執行先前已聲明感興趣的一個或多個操作。如果某個通道已准備就緒,則將注冊時所返回的鍵添加到該選擇器的已選擇鍵集中。為了確定每個通道已准備好執行哪些操作,可以檢查該鍵集和其中的鍵。為了執行所需的任何 I/O 操作,可根據每個鍵檢索相應的通道。
指示其通道對某個操作已准備就緒的選擇鍵只是一個提示,並不保證線程執行此種操作而不導致被阻塞。為了在這些提示證明不正確時忽略這些提示,則要強制寫入負責執行多路復用 I/O 操作的代碼。
此包定義了與 java .net 包中定義的 DatagramSocket、ServerSocket 和 Socket 類相對應的可選擇通道類。為了支持與通道相關的套接字,已經對這些類進行了較小的更改。此包還定義了實現單向管道的簡單類。在所有情況下,都是通過調用相應類的靜態 open 方法來創建新的可選擇通道。如果某個通道需要一個關聯的套接字,則此操作同時也創建一個套接字。
可通過「插入」一個在 java.nio.channels.spi 包中定義的 SelectorProvider 類的替代定義或實例來替換選擇器、可選通道和選擇鍵的實現。並不期望很多開發人員都會實際使用這些設施;提供它的主要目的是為了在需要極高的性能時,能夠讓經驗豐富的用戶能充分利用特定於操作系統的 I/O 多路復用機制。
實現多路復用 I/O 抽象所要求的很多簿記和同步任務由 java.nio.channels.spi 包中的 AbstractInterruptibleChannel、AbstractSelectableChannel、AbstractSelectionKey 和 AbstractSelector 類來執行。在定義一個自定義的選擇器提供程序時,應該只直接擴展 AbstractSelector 和 AbstractSelectionKey 類;自定義通道類應該擴展此包中定義的適當 SelectableChannel 子類。
從以下版本開始:
1.4