BIO/NIO底層原理分析由來腰懂?
兩個原因,一BIO/NIO的區(qū)別底層原理一直是Java程序員的一個難點、重點幼东,值得學(xué)習(xí),也必須要學(xué)習(xí)肮街;二最近想要入職BAT林束、TMD進行面試準(zhǔn)備,BIO/NIO基本是一個必考問題蹄梢。
BIO
BIO是什么疙筹?
Blocking IO,阻塞IO,同步阻塞IO
同步而咆?
自己去做某件事(Java自己親自做這件事霍比,并且是指同一個時間點)
阻塞?
類似銀行排隊去ATM取錢(Java進行IO操作暴备,一直會等到讀操作或者寫操作完成悠瞬,當(dāng)前線程才能去做其他事情)
廢話少說上代碼
package bio_nio.study;
import java.io .IOException;
import java.io .InputStream;
import java.io .OutputStream;
import java.net.Socket;
/**
* 客戶端
*/
public class FirstClient {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 8000);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello server, i am client".getBytes());
outputStream.flush();;
InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[1024];
int length = 0;
// 讀取服務(wù)端的數(shù)據(jù)
while ((length = inputStream.read(buffer)) > 0) {
System.out.println(new String(buffer, 0, length));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package bio_nio.study;
import java.io .IOException;
import java.io .InputStream;
import java.io .OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服務(wù)端
*/
public class FirstServer {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8000);
System.out.println("服務(wù)器啟動成功,監(jiān)聽端口8000");
// 不斷監(jiān)聽客戶端的請求
while (true) {
Socket socket = serverSocket.accept(); //阻塞
InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[1024];
int length = 0;
// 讀取客戶端的數(shù)據(jù)
while ((length = inputStream.read(buffer)) > 0) {
System.out.println(new String(buffer, 0, length));
}
// 向客戶端寫數(shù)據(jù)
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello java".getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
代碼分析:
單線程下的通信
- 1.同步:java
- 2.阻塞:
- accept 是阻塞的涯捻。
- IO進行操作的時候浅妆,要么讀操作,要么寫操作(前提:單線程環(huán)境下而言)障癌。
- BIO中不可能只有一個客戶端進行連接凌外,同步阻塞(阻塞主要描述的是IO本身的操作)。
單線程情況如此涛浙,那多線程下的BIO呢趴乡?
還是先上代碼:
package bio_nio.study;
import java.io .IOException;
import java.io .InputStream;
import java.io .OutputStream;
import java.net.Socket;
public class ServerHandler implements Runnable {
//維護一個socket成員變量,記錄傳來的socket
private Socket socket;
public ServerHandler (Socket socket) {
this.socket = socket;
}
//當(dāng)前線程要執(zhí)行的任務(wù)
@Override
public void run() {
InputStream inputStream = null;
try {
inputStream = socket.getInputStream();
byte[] buffer = new byte[1024];
int length = 0;
//讀取客戶端的數(shù)據(jù)
while ((length = inputStream.read(buffer)) > 0) {
System.out.println(new String(buffer, 0, length));
}
//向客戶端寫數(shù)據(jù)
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello java".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
package bio_nio.study;
import java.io .IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 多個Socket 多線程
*/
public class SecondServer {
public static void main (String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8000);
System.out.println("服務(wù)器啟動成功蝗拿,監(jiān)聽端口8000");
while (true) {
Socket socket = serverSocket.accept();
// 針對每個連接創(chuàng)建一個線程晾捏,去處理IO操作
new Thread(new ServerHandler(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
代碼分析:
多線程下的通信
存在的問題:
客戶端和服務(wù)端交互的本質(zhì)是數(shù)據(jù)的交流,不應(yīng)該在一有Socket連接的時候哀托,就進行new Thread惦辛;而應(yīng)該是在有IO操作的時候再去開啟線程。
再介紹一種方法:偽異步IO的方式
BIO中仓手,線程池已經(jīng)可以解決這種問題胖齐。
再上代碼:
package bio_nio.study;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* BIO 多個Socket多線程,由線程池執(zhí)行多個任務(wù)
*/
public class ThirdServer {
public static void main (String[] args) {
ServerSocket serverSocket;
ExecutorService executorService = Executors.newFixedThreadPool(60);
try {
serverSocket = new ServerSocket(8000);
System.out.println("服務(wù)器啟動成功嗽冒,監(jiān)聽端口8000");
while (true) {
Socket socket = serverSocket.accept();
//使用線程池中的線程去執(zhí)行每個對應(yīng)的任務(wù)
executorService.execute(new ServerHandler(socket));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
代碼分析:
線程池下的通信
- 線程池呀伙,進行IO操作的時候,從線程池中分配一個線程去執(zhí)行run任務(wù)添坊;
- 線程池只提供60個線程剿另,當(dāng)需求量超過60個線程的時候,則進行等待贬蛙;
線程池的管理方式帶來的問題雨女?
高并發(fā)的情況不適用(如果需要600個線程呢?總不能創(chuàng)建600個線程的線程池吧)阳准。
主角出場——NIO
NIO是什么氛堕?
New IO,Non-Blocking IO野蝇,同步非阻塞
非阻塞讼稚?
類似去銀行柜臺取錢括儒,先去取號,然后在銀行大廳等待叫號锐想。(客戶端請求連接服務(wù)端的時候帮寻,服務(wù)端將連接請求記錄下來,判斷是否要進行IO操作)
BIO存在問題痛倚?
- a.每一個socket連接服務(wù)端都會立刻開啟一個線程進行處理。(解決方案:連接澜躺,但不立刻開啟線程蝉稳,有IO操作再開啟線程)。
- b.每個IO操作完成后掘鄙,線程就會銷毀耘戚。(解決方案:不要輕易銷毀)
廢話少說,上代碼
package bio_nio.study;
import java.netInetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
private int port = 8000;
private InetSocketAddress address = null;
// 注冊客戶端連接信息
private Selector selector;
public NIOServer(int port) {
try {
this.port = port;
address = new InetSocketAddress(this.port);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(address);
// 服務(wù)器通道設(shè)置為非阻塞的模式
serverSocketChannel.configureBlocking(false);
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服務(wù)器啟動:" + this.port);
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* Selector開始輪詢
*/
public void listen() {
try {
//輪詢
while(true) {
//在Selector上連接的數(shù)量
int wait = this.selector.select(); //類似accept()是阻塞的
if(wait == 0) continue;
Set<SelectionKey> keys = this.selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while(iterator.hasNext()) {
SelectionKey key = iterator.next();
//針對每一個客戶端進行相應(yīng)的操作
process(key);
iterator.remove();
}
}
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* 處理每一個客戶端的請求
*/
public void process(SelectionKey key) throws Exception {
ByteBuffer buffer = ByteBuffer.allocate(1024);
if(key.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
// 客戶端一旦連接上來操漠,進行讀寫操作
// 往這個Selector上注冊Key收津,OP_READ,接下來可以進行讀操作
socketChannel.register(selector, SelectionKey.OP_READ);
} else if(key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
int len = socketChannel.read(buffer);
if(len > 0) {
buffer.flip();
String content = new String(buffer.array(), 0, len);
System.out.println(content);
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
buffer.clear();
} else if(key.isWritable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
socketChannel.write(buffer.wrap("hello nio".getBytes()));
socketChannel.close();
}
}
代碼分析:
NIO下的通信
- 在服務(wù)端有一個線程浊伙,用于記錄客戶端的連接操作撞秋。
- BIO與NIO區(qū)別
BIO:ServerSocket、Socket
NIO:ServerSocketChannel服務(wù)端 8000嚣鄙、SocketChannel客戶端吻贿、class Selector {}記錄客戶端的一個狀態(tài) - NIO主要功勞,一Selector機制哑子,二底層使用了Buffer舅列,Buffer構(gòu)造如圖:
Buffer構(gòu)造
推薦及參考:
可能大家依舊覺得NIO很麻煩,自己使用起來也不是很順手卧蜓。首先原理必須要知道帐要,其次Java中的大牛們給我們提供了一個開源框架——netty框架,對NIO的進行了一個很好的封裝弥奸,感興趣可以了解下榨惠。
參考資料:阿里面試必備之分析IO及NIO的底層原理