1. 程式人生 > >Java的阻塞與非同步模型

Java的阻塞與非同步模型

From Blog:Java——BIO、NIO、AIO
執行緒會阻塞在什麼地方?呼叫讀取檔案的API從硬碟上讀取檔案時,通過網路訪問API從網路上請求資源時,等待獲取到需要的資料以後再進行後續的處理。那麼我們應該怎樣線上程阻塞的時候讓這個執行緒去幹別的事呢,然後等資料來了以後再進行後續的資料處理。傳統的程式設計模型肯定是不可能的,因為你不能讓執行緒主動去執行別的程式碼,而且你怎麼知道你需要的資料準備好了,如果資料準備好了,應該能夠通知執行緒進行後續的處理。如果按照常規的想法,要實現非同步執行,需要執行緒能被主動排程,而且還要有事件通知機制確定執行緒被排程的時機。
NIO和傳統IO(一下簡稱IO)之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。從這句話上可以看出,NIO多少還是採用了生產者消費者模式,生產者生產資料,消費者消費資料,哪個生產者生產出資料,就會有消費者消費掉,它們之間採用緩衝區來進行耦合。這種模式其實就是非同步模式的一種。

參見部落格:Java NIO?看這一篇就夠了!
傳統的IO模型是一個執行緒處理一個請求,NIO可以一個執行緒同時處理多個請求,當然,一個執行緒是不能同時處理多個請求的,但是多個請求並不需要同時處理,每次只處理準備好的請求,沒有準備好的請求暫不處理,這樣就需要一個輪詢機制來從多個請求當中選出準備好的請求來處理。
NIO示意圖
詳見部落格:深入理解Java-NIO
可以看到,NIO並不是為了解決單執行緒阻塞的問題,而是把所有阻塞的操作都交由一個執行緒處理,可以看到,我們並沒有去排程執行緒,而是去排程通道(Channel),因為我們並不能主動去排程執行緒執行某個任務,只能採用這種方法。通道是用來排程的,儲存了Channel當前的狀態,並不能儲存資料,所以我們需要一個容器來接受通過Chennel獲取到的資料,這個容器我們就叫做Buffer,也被稱作緩衝區。可以看到,所有的Channel都可以公用一個快取區,因為同一時間只有一個Channel得到處理(單執行緒模型)。
From Blog:
java中的AIO

可以看到,上面的過程需要我們手動去輪詢準備好的Channel,我們其實只需要關注Channel準備好以後獲取到讀取的資料以後我們需要幹什麼,我們其實關注的只是獲取到的資料。我們期望的其實是類似於JS中的回撥的那種寫法。jdk7中新增了一些與檔案(網路)I/O相關的一些api。這些API被稱為NIO.2,或稱為AIO(Asynchronous I/O)。具體的寫法跟JS中的回撥非常相似,程式碼如下:

  final AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel
            .
open() .bind(new InetSocketAddress("0.0.0.0",8888)); channel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() { @Override public void completed(final AsynchronousSocketChannel client, Void attachment) { channel.accept(null, this); ByteBuffer buffer = ByteBuffer.allocate(1024); client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result_num, ByteBuffer attachment) { attachment.flip(); CharBuffer charBuffer = CharBuffer.allocate(1024); CharsetDecoder decoder = Charset.defaultCharset().newDecoder(); decoder.decode(attachment,charBuffer,false); charBuffer.flip(); String data = new String(charBuffer.array(),0, charBuffer.limit()); System.out.println("read data:" + data); try{ client.close(); }catch (Exception e){} } @Override public void failed(Throwable exc, ByteBuffer attachment) { System.out.println("read error"); } }); } @Override public void failed(Throwable exc, Void attachment) { System.out.println("accept error"); } }); while (true){ Thread.sleep(1000); }

再附上一篇博文:Java NIO:淺析I/O模型
但是,在JS中我們知道,回撥會造成Callback Hell(回撥地獄),那麼這個在JS中是怎麼解決的呢?
參見以下部落格,說的很詳細:
ES6之async+await同步/非同步方案詳解
自學-ES6篇-非同步操作和Async函式
js-ES6學習筆記-async函式
還有一種單執行緒併發的模型,叫做協成,在GO語言中用的比較多:
Golang教程:(二十一)協程
還有設計模式:Python 中最簡最好的設計模式