欧美成人午夜免费全部完,亚洲午夜福利精品久久,а√最新版在线天堂,另类亚洲综合区图片小说区,亚洲欧美日韩精品色xxx

千鋒扣丁學堂Java培訓之探索IO模型總結(jié)整理

2019-06-13 13:42:47 1158瀏覽

今天千鋒扣丁學堂Java培訓老師給大家分享一篇關(guān)于探索Java開發(fā)I/O模型演進的詳細介紹,什么是同步?什么是異步?阻塞和非阻塞又有什么區(qū)別?本文先從Unix的I/O模型講起,介紹了5種常見的I/O模型。而后再引出Java的I/O模型的演進過程,并用實例說明如何選擇合適的JavaI/O模型來提高系統(tǒng)的并發(fā)量和可用性。



同步和異步,描述的是用戶線程與內(nèi)核的交互方式:

同步是指用戶線程發(fā)起I/O請求后需要等待或者輪詢內(nèi)核I/O操作完成后才能繼續(xù)執(zhí)行;

異步是指用戶線程發(fā)起I/O請求后仍繼續(xù)執(zhí)行,當內(nèi)核I/O操作完成后會通知用戶線程,或者調(diào)用用戶線程注冊的回調(diào)函數(shù)。

阻塞和非阻塞,描述的是用戶線程調(diào)用內(nèi)核I/O操作的方式:

阻塞是指I/O操作需要徹底完成后才返回到用戶空間;

非阻塞是指I/O操作被調(diào)用后立即返回給用戶一個狀態(tài)值,無需等到I/O操作徹底完成。

一個I/O操作其實分成了兩個步驟:發(fā)起I/O請求和實際的I/O操作。阻塞I/O和非阻塞I/O的區(qū)別在于第一步,發(fā)起I/O請求是否會被阻塞,如果阻塞直到完成那么就是傳統(tǒng)的阻塞I/O,如果不阻塞,那么就是非阻塞I/O。同步I/O和異步I/O的區(qū)別就在于第二個步驟是否阻塞,如果實際的I/O讀寫阻塞請求進程,那么就是同步I/O。

UnixI/O模型

Unix下共有五種I/O模型:

阻塞I/O

非阻塞I/O

I/O復用(select和poll)

信號驅(qū)動I/O(SIGIO)

異步I/O(POSIX的aio_系列函數(shù))

阻塞I/O

請求無法立即完成則保持阻塞。

階段1:等待數(shù)據(jù)就緒。網(wǎng)絡I/O的情況就是等待遠端數(shù)據(jù)陸續(xù)抵達;磁盤I/O的情況就是等待磁盤數(shù)據(jù)從磁盤上讀取到內(nèi)核態(tài)內(nèi)存中。

階段2:數(shù)據(jù)從內(nèi)核拷貝到進程。出于系統(tǒng)安全,用戶態(tài)的程序沒有權(quán)限直接讀取內(nèi)核態(tài)內(nèi)存,因此內(nèi)核負責把內(nèi)核態(tài)內(nèi)存中的數(shù)據(jù)拷貝一份到用戶態(tài)內(nèi)存中。

非阻塞I/O

socket設置為NONBLOCK(非阻塞)就是告訴內(nèi)核,當所請求的I/O操作無法完成時,不要將進程睡眠,而是返回一個錯誤碼(EWOULDBLOCK),這樣請求就不會阻塞

I/O操作函數(shù)將不斷的測試數(shù)據(jù)是否已經(jīng)準備好,如果沒有準備好,繼續(xù)測試,直到數(shù)據(jù)準備好為止。整個I/O請求的過程中,雖然用戶線程每次發(fā)起I/O請求后可以立即返回,但是為了等到數(shù)據(jù),仍需要不斷地輪詢、重復請求,消耗了大量的CPU的資源

數(shù)據(jù)準備好了,從內(nèi)核拷貝到用戶空間。

一般很少直接使用這種模型,而是在其他I/O模型中使用非阻塞I/O這一特性。這種方式對單個I/O請求意義不大,但給I/O多路復用鋪平了道路.

I/O復用(異步阻塞I/O)

I/O多路復用會用到select或者poll函數(shù),這兩個函數(shù)也會使進程阻塞,但是和阻塞I/O所不同的的,這兩個函數(shù)可以同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O函數(shù)進行檢測,直到有數(shù)據(jù)可讀或可寫時,才真正調(diào)用I/O操作函數(shù)。



從流程上來看,使用select函數(shù)進行I/O請求和同步阻塞模型沒有太大的區(qū)別,甚至還多了添加監(jiān)視socket,以及調(diào)用select函數(shù)的額外操作,效率更差。但是,使用select以后最大的優(yōu)勢是用戶可以在一個線程內(nèi)同時處理多個socket的I/O請求。用戶可以注冊多個socket,然后不斷地調(diào)用select讀取被激活的socket,即可達到在同一個線程內(nèi)同時處理多個I/O請求的目的。而在同步阻塞模型中,必須通過多線程的方式才能達到這個目的。

I/O多路復用模型使用了Reactor設計模式實現(xiàn)了這一機制。

調(diào)用select/poll該方法由一個用戶態(tài)線程負責輪詢多個socket,直到某個階段1的數(shù)據(jù)就緒,再通知實際的用戶線程執(zhí)行階段2的拷貝。通過一個專職的用戶態(tài)線程執(zhí)行非阻塞I/O輪詢,模擬實現(xiàn)了階段一的異步化

信號驅(qū)動I/O(SIGIO)

首先我們允許socket進行信號驅(qū)動I/O,并安裝一個信號處理函數(shù),進程繼續(xù)運行并不阻塞。當數(shù)據(jù)準備好時,進程會收到一個SIGIO信號,可以在信號處理函數(shù)中調(diào)用I/O操作函數(shù)處理數(shù)據(jù)。



異步I/O

調(diào)用aio_read函數(shù),告訴內(nèi)核描述字,緩沖區(qū)指針,緩沖區(qū)大小,文件偏移以及通知的方式,然后立即返回。當內(nèi)核將數(shù)據(jù)拷貝到緩沖區(qū)后,再通知應用程序。



異步I/O模型使用了Proactor設計模式實現(xiàn)了這一機制。

告知內(nèi)核,當整個過程(包括階段1和階段2)全部完成時,通知應用程序來讀數(shù)據(jù).

幾種I/O模型的比較

前四種模型的區(qū)別是階段1不相同,階段2基本相同,都是將數(shù)據(jù)從內(nèi)核拷貝到調(diào)用者的緩沖區(qū)。而異步I/O的兩個階段都不同于前四個模型。

同步I/O操作引起請求進程阻塞,直到I/O操作完成。異步I/O操作不引起請求進程阻塞。



常見JavaI/O模型

在了解了UNIX的I/O模型之后,其實Java的I/O模型也是類似。

“阻塞I/O”模式

在上一節(jié)Socket章節(jié)中的EchoServer就是一個簡單的阻塞I/O例子,服務器啟動后,等待客戶端連接。在客戶端連接服務器后,服務器就阻塞讀寫取數(shù)據(jù)流。

EchoServer代碼:

public class EchoServer {
public static int DEFAULT_PORT = 7;
 
public static void main(String[] args) throws IOException {
 
int port;
try {
port = Integer.parseInt(args[0]);
} catch (RuntimeException ex) {
port = DEFAULT_PORT;
}
try (
ServerSocket serverSocket =
new ServerSocket(port);
Socket clientSocket = serverSocket.accept(); 
PrintWriter out =
new PrintWriter(clientSocket.getOutputStream(), true); 
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
out.println(inputLine);
}
} catch (IOException e) {
System.out.println("Exception caught when trying to listen on port "
+ port + " or listening for a connection");
System.out.println(e.getMessage());
}
}
}

改進為“阻塞I/O+多線程”模式

使用多線程來支持多個客戶端來訪問服務器。

主線程MultiThreadEchoServer.java

public class MultiThreadEchoServer {
public static int DEFAULT_PORT = 7;
public static void main(String[] args) throws IOException {
int port;
try {
port = Integer.parseInt(args[0]);
} catch (RuntimeException ex) {
port = DEFAULT_PORT;
}
Socket clientSocket = null;
try (ServerSocket serverSocket = new ServerSocket(port);) {
while (true) {
clientSocket = serverSocket.accept();
// MultiThread
new Thread(new EchoServerHandler(clientSocket)).start();
}
} catch (IOException e) {
System.out.println(
"Exception caught when trying to listen on port " + port + " or listening for a connection");
System.out.println(e.getMessage());
}
}
}

處理器類EchoServerHandler.java

public class EchoServerHandler implements Runnable {
private Socket clientSocket;
public EchoServerHandler(Socket clientSocket) {
this.clientSocket = clientSocket;
}
@Override
public void run() {
try (PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
out.println(inputLine);
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}

存在問題:每次接收到新的連接都要新建一個線程,處理完成后銷毀線程,代價大。當有大量地短連接出現(xiàn)時,性能比較低。

改進為“阻塞I/O+線程池”模式

針對上面多線程的模型中,出現(xiàn)的線程重復創(chuàng)建、銷毀帶來的開銷,可以采用線程池來優(yōu)化。每次接收到新連接后從池中取一個空閑線程進行處理,處理完成后再放回池中,重用線程避免了頻率地創(chuàng)建和銷毀線程帶來的開銷。

主線程ThreadPoolEchoServer.java

public class ThreadPoolEchoServer {
public static int DEFAULT_PORT = 7;
public static void main(String[] args) throws IOException {
int port;
try {
port = Integer.parseInt(args[0]);
} catch (RuntimeException ex) {
port = DEFAULT_PORT;
}
ExecutorService threadPool = Executors.newFixedThreadPool(5);
Socket clientSocket = null;
try (ServerSocket serverSocket = new ServerSocket(port);) {
while (true) {
clientSocket = serverSocket.accept();
// Thread Pool
threadPool.submit(new Thread(new EchoServerHandler(clientSocket)));
}
} catch (IOException e) {
System.out.println(
"Exception caught when trying to listen on port " + port + " or listening for a connection");
System.out.println(e.getMessage());
}
}
}

存在問題:在大量短連接的場景中性能會有提升,因為不用每次都創(chuàng)建和銷毀線程,而是重用連接池中的線程。但在大量長連接的場景中,因為線程被連接長期占用,不需要頻繁地創(chuàng)建和銷毀線程,因而沒有什么優(yōu)勢。

雖然這種方法可以適用于小到中度規(guī)模的客戶端的并發(fā)數(shù),如果連接數(shù)超過100,000或更多,那么性能將很不理想。

改進為“非阻塞I/O”模式

“阻塞I/O+線程池”網(wǎng)絡模型雖然比”阻塞I/O+多線程”網(wǎng)絡模型在性能方面有提升,但這兩種模型都存在一個共同的問題:讀和寫操作都是同步阻塞的,面對大并發(fā)(持續(xù)大量連接同時請求)的場景,需要消耗大量的線程來維持連接。CPU在大量的線程之間頻繁切換,性能損耗很大。一旦單機的連接超過1萬,甚至達到幾萬的時候,服務器的性能會急劇下降。

而NIO的Selector卻很好地解決了這個問題,用主線程(一個線程或者是CPU個數(shù)的線程)保持住所有的連接,管理和讀取客戶端連接的數(shù)據(jù),將讀取的數(shù)據(jù)交給后面的線程池處理,線程池處理完業(yè)務邏輯后,將結(jié)果交給主線程發(fā)送響應給客戶端,少量的線程就可以處理大量連接的請求。

JavaNIO由以下幾個核心部分組成:

Channel

Buffer

Selector

要使用Selector,得向Selector注冊Channel,然后調(diào)用它的select()方法。這個方法會一直阻塞到某個注冊的通道有事件就緒。一旦這個方法返回,線程就可以處理這些事件,事件的例子有如新連接進來,數(shù)據(jù)接收等。

主線程NonBlokingEchoServer.java

public class NonBlokingEchoServer {
public static int DEFAULT_PORT = 7;
public static void main(String[] args) throws IOException {
int port;
try {
port = Integer.parseInt(args[0]);
} catch (RuntimeException ex) {
port = DEFAULT_PORT;
}
System.out.println("Listening for connections on port " + port);
ServerSocketChannel serverChannel;
Selector selector;
try {
serverChannel = ServerSocketChannel.open();
InetSocketAddress address = new InetSocketAddress(port);
serverChannel.bind(address);
serverChannel.configureBlocking(false);
selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException ex) {
ex.printStackTrace();
return;
}
while (true) {
try {
selector.select();
} catch (IOException ex) {
ex.printStackTrace();
break;
}
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
try {
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
System.out.println("Accepted connection from " + client);
client.configureBlocking(false);
SelectionKey clientKey = client.register(selector,
SelectionKey.OP_WRITE | SelectionKey.OP_READ);
ByteBuffer buffer = ByteBuffer.allocate(100);
clientKey.attach(buffer);
}
if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer output = (ByteBuffer) key.attachment();
client.read(output);
}
if (key.isWritable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer output = (ByteBuffer) key.attachment();
output.flip();
client.write(output);
output.compact();
}
} catch (IOException ex) {
key.cancel();
try {
key.channel().close();
} catch (IOException cex) {
}
}
}
}
}
}

改進為“異步I/O”模式

JavaSE7版本之后,引入了異步I/O(NIO.2)的支持,為構(gòu)建高性能的網(wǎng)絡應用提供了一個利器。

主線程AsyncEchoServer.java

public class AsyncEchoServer {
public static int DEFAULT_PORT = 7;
public static void main(String[] args) throws IOException {
int port;
try {
port = Integer.parseInt(args[0]);
} catch (RuntimeException ex) {
port = DEFAULT_PORT;
}
ExecutorService taskExecutor = Executors.newCachedThreadPool(Executors.defaultThreadFactory());
// create asynchronous server socket channel bound to the default group
try (AsynchronousServerSocketChannel asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open()) {
if (asynchronousServerSocketChannel.isOpen()) {
// set some options
asynchronousServerSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 4 * 1024);
asynchronousServerSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
// bind the server socket channel to local address
asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
// display a waiting message while ... waiting clients
System.out.println("Waiting for connections ...");
while (true) {
Future<AsynchronousSocketChannel> asynchronousSocketChannelFuture = asynchronousServerSocketChannel
.accept();
try {
final AsynchronousSocketChannel asynchronousSocketChannel = asynchronousSocketChannelFuture
.get();
Callable<String> worker = new Callable<String>() {
@Override
public String call() throws Exception {
String host = asynchronousSocketChannel.getRemoteAddress().toString();
System.out.println("Incoming connection from: " + host);
final ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
// transmitting data
while (asynchronousSocketChannel.read(buffer).get() != -1) {
buffer.flip();
asynchronousSocketChannel.write(buffer).get();
if (buffer.hasRemaining()) {
buffer.compact();
} else {
buffer.clear();
}
}
asynchronousSocketChannel.close();
System.out.println(host + " was successfully served!");
return host;
}
};
taskExecutor.submit(worker);
} catch (InterruptedException | ExecutionException ex) {
System.err.println(ex);
System.err.println("\n Server is shutting down ...");
// this will make the executor accept no new threads
// and finish all existing threads in the queue
taskExecutor.shutdown();
// wait until all threads are finished
while (!taskExecutor.isTerminated()) {
}
break;
}
}
} else {
System.out.println("The asynchronous server-socket channel cannot be opened!");
}
} catch (IOException ex) {
System.err.println(ex);
}
}
}

本章例子的源碼,可以在https://github.com/waylau/essential-java中com.waylau.essentialjava.net.echo包下找到。

以上就是關(guān)于千鋒扣丁學堂Java培訓之探索IO模型總結(jié)整理的全部內(nèi)容,希望對大家的學習有所幫助,想要了解更多關(guān)于Java開發(fā)方面內(nèi)容的小伙伴,請關(guān)注扣丁學堂Java培訓官網(wǎng)、微信等平臺,扣丁學堂IT職業(yè)在線學習教育有專業(yè)的Java講師為您指導,此外扣丁學堂老師精心推出的Java視頻教程定能讓你快速掌握Java從入門到精通開發(fā)實戰(zhàn)技能??鄱W堂Java技術(shù)交流群:850353792。


                        JavaEE/微服務/源碼解析/分布式/企業(yè)級架構(gòu)【VIP體驗課】


     【關(guān)注微信公眾號獲取更多學習資料】       【掃碼進入JavaEE/微服務VIP免費公開課】  



查看更多關(guān)于“Java開發(fā)資訊”的相關(guān)文章>>

標簽: Java培訓 Java視頻教程 Java多線程 Java面試題 Java學習視頻 Java開發(fā)

熱門專區(qū)

暫無熱門資訊

課程推薦

微信
微博
15311698296

全國免費咨詢熱線

郵箱:codingke@1000phone.com

官方群:148715490

北京千鋒互聯(lián)科技有限公司版權(quán)所有   北京市海淀區(qū)寶盛北里西區(qū)28號中關(guān)村智誠科創(chuàng)大廈4層
京ICP備2021002079號-2   Copyright ? 2017 - 2022
返回頂部 返回頂部