要清楚什麼是NIO,首先要清楚什麼是BIO,即阻塞IO,看一段代碼:

ServerSocket serverSocket = new ServerSocket(port);

socket= serverSocket.accept();

OutputStream os = null;

InputStream is = null;try {

is = socket.getInputStream();

byte[] b = new byte[1024]; int n = is.read(b); os = socket.getOutputStream(); os.write(b, 0, n);

}

上面的代碼中有兩處阻塞的地方,一個是accept函數會調用到一個native方法accept0(nativefd, isaa);直到建立新的tcp連接。另外一處是read函數在沒有消息的時候會一直阻塞,直到接收到新的消息。java在1.4以前沒有NIO的時候處理網路消息的辦法就只能是每次有一個新的連接,開啟一個新的線程,或者從線程池中取出一個線程,這個線程執行的邏輯是使用一個while循環來不斷接收消息,接收到消息以後處理消息或者加入消息隊列交給其他線程處理。

即:

new Thread(()->{

while (true){

is = socket.getInputStream();

byte[] b = new byte[1024]; int n = is.read(b); //向客戶端發送反饋內容

os = socket.getOutputStream();

os.write(b, 0, n); }});

現在來到了我們的NIO,NIO在Linux上使用了epoll這個系統調用,epoll能夠做到已註冊的連接在消息到來的時候主動通知調度器,將消息加入消息隊列。這個調度器就是java NIO的Selector,Selector的select設定一個超時時間,獲取消息隊列中的消息然後分發給工作線程非同步進行解碼等操作。

NIO將上面的代碼分給了至少三個線程去完成,一個處理連接的線程,一個消息調度線程,以及至少一個工作線程。NIO降低了任務的粒度並且避免阻塞io中大量的線程阻塞佔用過多的內存。實際代碼中,工作線程們不能直接處理佔用過多cpu時間的操作,應當將處理好的數據放入消息隊列或者直接交給非同步線程池來處理,否則會阻塞後續消息的接收,又回到了阻塞io的問題上。

NIO,即非阻塞io最重要的其實就是解決了read函數的阻塞問題,然後分離讀消息與處理消息(類似與業務中專門使用sql處理線程池非同步處理sql相關的處理)減少大量的線程佔用。

類比go的協程,協程佔用資源更小,可以大量開闢,很大程度上就不需要像java一樣降低業務粒度了,所以也意味著編寫並發程序更加的簡便。


推薦閱讀:
相關文章