阻塞IO 和非阻塞IO 這兩個概念是程序級別的。主要描述的是程序請求操做系統IO操做後,若是IO資源沒有準備好,那麼程序該如何處理的問題:前者等待;後者繼續執行(可是使用線程一直輪詢,直到有IO資源準備好了)。html
同步IO 和 異步IO,這兩個概念是操做系統級別的。主要描述的是操做系統在收到程序請求IO操做後,若是IO資源沒有準備好,該如何響應程序的問題:前者不響應,直到IO資源準備好之後;後者返回一個標記(好讓程序和本身知道之後的數據往哪裏通知),當IO資源準備好之後,再用事件機制返回給程序。java
同步阻塞IO模型是最簡單的IO模型,用戶線程在內核進行IO操做時若是數據沒有準備號會被阻塞。git
僞代碼表示以下:github
{
// 阻塞,直到有數據
read(socket, buffer);
process(buffer);
}
複製代碼
BIO通訊方式的特色
:面試
BIO通訊方式在單線程服務器下一次只能處理一個請求,在處理完畢以前一直阻塞。所以不適用於高併發的狀況。不過可使用多線程稍微
改進。服務器
Java中的阻塞模式BIO,就是在java.net
包中的Socket套接字的實現,Socket套接字是TCP/UDP等傳輸層協議的實現。網絡
爲了測試服務端程序,能夠先編寫一個多線程客戶端用於請求測試。多線程
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.CountDownLatch;
/** * <p> * BIO測試 * 模擬20個客戶端併發請求,服務端則使用單線程。 * * @Author niujinpeng * @Date 2018/10/15 10:50 */
public class SocketClient {
public static void main(String[] args) throws InterruptedException {
Integer clientNumber = 20;
CountDownLatch countDownLatch = new CountDownLatch(clientNumber);
// 分別啓動20個客戶端
for (int index = 0; index < clientNumber; index++, countDownLatch.countDown()) {
SocketClientRequestThread client = new SocketClientRequestThread(countDownLatch, index);
new Thread(client).start();
}
synchronized (SocketClient.class) {
SocketClient.class.wait();
}
}
}
/** * <p> * 客戶端,用於模擬請求 * * @Author niujinpeng * @Date 2018/10/15 10:53 */
class SocketClientRequestThread implements Runnable {
private CountDownLatch countDownLatch;
/** * 線程的編號 */
private Integer clientIndex;
public SocketClientRequestThread(CountDownLatch countDownLatch, Integer clientIndex) {
this.countDownLatch = countDownLatch;
this.clientIndex = clientIndex;
}
@Override
public void run() {
Socket socket = null;
OutputStream clientRequest = null;
InputStream clientResponse = null;
try {
socket = new Socket("localhost", 83);
clientRequest = socket.getOutputStream();
clientResponse = socket.getInputStream();
//等待,直到SocketClientDaemon完成全部線程的啓動,而後全部線程一塊兒發送請求
this.countDownLatch.await();
// 發送請求信息
clientRequest.write(("這是第" + this.clientIndex + "個客戶端的請求").getBytes());
clientRequest.flush();
// 等待服務器返回消息
System.out.println("第" + this.clientIndex + "個客戶端請求發送完成,等待服務器響應");
int maxLen = 1024;
byte[] contentBytes = new byte[maxLen];
int realLen;
String message = "";
// 等待服務端返回,in和out不能cloese
while ((realLen = clientResponse.read(contentBytes, 0, maxLen)) != -1) {
message += new String(contentBytes, 0, realLen);
}
System.out.println("第" + this.clientIndex + "個客戶端接受到來自服務器的消息:" + message);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
try {
if (clientRequest != null) {
clientRequest.close();
}
if (clientRequest != null) {
clientResponse.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
複製代碼
由於Java中的Socket就是BIO的模式,所以咱們能夠很簡單的編寫一個BIO單線程服務端。架構
SocketServer.java併發
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/** * BIO服務端 * <p> * 單線程阻塞的服務器端 * * @Author niujinpeng * @Date 2018/10/15 11:17 */
public class SocketServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(83);
try {
while (true) {
// 阻塞,直到有數據準備完畢
Socket socket = serverSocket.accept();
// 開始收取信息
InputStream input = socket.getInputStream();
OutputStream output = socket.getOutputStream();
Integer sourcePort = socket.getPort();
int maxLen = 1024 * 2;
byte[] contextBytes = new byte[maxLen];
// 阻塞,直到有數據準備完畢
int realLen = input.read(contextBytes, 0, maxLen);
// 讀取信息
String message = new String(contextBytes, 0, realLen);
// 輸出接收信息
System.out.println("服務器收到來自端口【" + sourcePort + "】的信息:" + message);
// 響應信息
output.write("Done!".getBytes());
// 關閉
output.close();
input.close();
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
serverSocket.close();
}
}
}
}
複製代碼
單線程服務器,在處理請求時只能同時處理一條,也就是說若是在請求到來時發現有請求還沒有處理完畢,只能等待處理,所以使用多線程改進
服務端。
SocketServerThread.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/** * BIO服務端 * <p> * 多線程的阻塞的服務端 * <p> * 固然,接收到客戶端的socket後,業務的處理過程能夠交給一個線程來作。 * 但仍是改變不了socket被一個一個的作accept()的狀況。 * * @Author niujinpeng * @Date 2018/10/15 11:17 */
public class SocketServerThread implements Runnable {
/** * 日誌 */
private static final Logger logger = LoggerFactory.getLogger(SocketServerThread.class);
private Socket socket;
public SocketServerThread(Socket socket) {
this.socket = socket;
}
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(83);
try {
while (true) {
Socket socket = serverSocket.accept();
//固然業務處理過程能夠交給一個線程(這裏可使用線程池),而且線程的建立是很耗資源的。
//最終改變不了.accept()只能一個一個接受socket的狀況,而且被阻塞的狀況
SocketServerThread socketServerThread = new SocketServerThread(socket);
new Thread(socketServerThread).start();
}
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
if (serverSocket != null) {
serverSocket.close();
}
}
}
@Override
public void run() {
InputStream in = null;
OutputStream out = null;
try {
//下面咱們收取信息
in = socket.getInputStream();
out = socket.getOutputStream();
Integer sourcePort = socket.getPort();
int maxLen = 1024;
byte[] contextBytes = new byte[maxLen];
//使用線程,一樣沒法解決read方法的阻塞問題,
//也就是說read方法處一樣會被阻塞,直到操做系統有數據準備好
int realLen = in.read(contextBytes, 0, maxLen);
//讀取信息
String message = new String(contextBytes, 0, realLen);
//下面打印信息
logger.info("服務器收到來自於端口:" + sourcePort + "的信息:" + message);
//下面開始發送信息
out.write("回發響應信息!".getBytes());
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
//試圖關閉
try {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
if (this.socket != null) {
this.socket.close();
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
}
}
複製代碼
看起來多線程增長了服務能力,可是很明顯多線程改進以後仍有如下侷限性
:
cat /proc/sys/kernel/threads-max
能夠查看能夠建立的線程數量。調用底層系統
的同步IO
而決定的同步IO機制。BIO模式由於進程的阻塞掛起,不會消耗過多的CPU資源,並且開發難度低,比較適合併發量小的網絡應用開發。同時很容易發現由於請求IO會阻塞進程,因此不時候併發量大的應用。若是爲每個請求分配一個線程,系統開銷就會過大。
同時在Java中,使用了多線程來處理阻塞模式,也沒法解決程序在accept()
和read()
時候的阻塞問題。由於accept()
和read()
的IO模式支持是基於操做系統的,若是操做系統發現沒有套接字從指定的端口傳送過來,那麼操做系統就會等待
。這樣accept()
和read()
方法就會一直等待。
GitHub 源碼:github.com/niumoo/java…
此文參考文章:5種IO模型、阻塞IO和非阻塞IO、同步IO和異步IO
此文參考文章:架構設計:系統間通訊(3)——IO通訊模型和JAVA實踐 上篇
<完>
我的網站:www.codingme.net
若是你喜歡這篇文章,能夠關注公衆號,一塊兒成長。 關注公衆號回覆資源能夠沒有套路的獲取全網最火的的 Java 核心知識整理&面試核心資料。