Java NIO之理解I/O模型(一)

  • 2019 年 10 月 3 日
  • 筆記

前言

自己以前在Java NIO這塊兒,一直都是比較薄弱的,以前還因為這點知識而錯失了一個機會。所以最近打算好好學習一下這部分內容,我想應該也會有朋友像我一樣,一直想鬧明白這塊兒內容。但是一直無從下手,每次被問到什麼NIO,BIO,AIO就慌,下面我們先從一些基本概念來慢慢了解NIO這部分內容。

同步與非同步

同步和非同步是比較好理解的,網上也有好多解釋。下面我通過個人的理解來解釋這兩個概念可能會通俗一些,希望能更好理解。

同步就是多個任務或事件在執行時需要按順序逐個執行,如果排在順序前面的任務或事件在執行的時候,排在後面的任務或事件就需要等待前面的執行完後才可以執行,這些任務或事件是不能並行執行的。同步執行任務可以被設計為可靠的任務序列,前後兩個任務可以保持一致才算整個任務結束。

非同步是多個任務或事件可以同時並行執行,前面的任務不會導致後面的任務的等待。因為是多個任務同時進行的,所以每個任務之間不產生相互的依賴,所以無法保證可靠性。

同步流程圖

非同步流程圖

同步示例程式碼

public static void test1(){      System.out.println(">>>>>>>>>test1<<<<<<<<<<");  }  public static void test2(){      System.out.println(">>>>>>>>>test2<<<<<<<<<<");  }  public static void test3(){      System.out.println(">>>>>>>>>test3<<<<<<<<<<");  }  public static void main(String[] args) {          test1();          test2();          test3();  }

按順序逐個執行的方法,test3會等待test1和test2都執行完後再執行。

非同步示例程式碼

public static void testA(){          new Thread(){              @Override              public void run() {                  System.out.println(">>>>>>>>>testA<<<<<<<<<<");              }          }.start();  }  public static void testB(){          new Thread(){              @Override              public void run() {                  System.out.println(">>>>>>>>>testB<<<<<<<<<<");              }          }.start();  }  public static void testC(){          new Thread(){              @Override              public void run() {                  System.out.println(">>>>>>>>>testC<<<<<<<<<<");              }          }.start();  }  public static void main(String[] args) {          testA();          testB();          testC();  }

上面這段非同步程式碼可以看出,testA、testB、testC三個方法各自有自己的執行緒來執行任務,互相不依賴所以不會造成有任務等待的情況。典型的非同步處理機制。

雖然上面的非同步用了三個執行緒來實現了,但是並不代表多執行緒就是非同步,這是兩個概念,多執行緒只是實現非同步的一種方式。而非同步是一種處理模式,除了多執行緒還可以有其他的方式來實現。

在生活中的例子我們在打電話的時候就相當於同步,只有對方接通了才算任務執行成功。而發簡訊則是非同步,簡訊發送後並不依賴接收者是否接收成功。

阻塞與非阻塞

阻塞是指當有任務在執行時,會發出一個請求操作,如果該請求操作需要的條件不滿足的話,那麼就會一直等待,直到條件滿足後,才繼續執行後面的其他工作

非阻塞是指當有任務在執行時,會發出一個請求操作,如果該請求操作需要的條件不滿足的話,會立即返回一個標誌資訊告知條件不滿足,而不會一直在等待下去

阻塞流程

 

非阻塞流程

 

有的人總是把同步、非同步,與阻塞、非阻塞, 這兩組概念給理解混了,但是其實這是兩組完全不同的概念。

同步與非同步這組概念的重點在於,前面的任務是否會導致整個流程的等待。

阻塞與非阻塞這組概念的重點在於,如果操作請求不滿足條件是否會返回一個標誌資訊告知不滿足條件。

其實理解阻塞與非阻塞可以從我們通常所接觸的執行緒阻塞來理解,當出現慢任務的時候,執行緒會發生阻塞,cpu會等待慢任務執行完成後再執行後續的任務。而非阻塞執行緒在執行這個慢任務的時候,會去做其他事情,當慢任務執行完成後,再去執行後面的任務。非阻塞雖然看似可以明顯提高效率,但是系統的執行緒切換也是會造成時間損耗,所以需要合理利用。

同步IO與非同步IO

同步IO是指,當一個執行緒在執行IO操作時,該執行緒在IO操作完成前,是會被阻塞的。

非同步IO是指,當一個執行緒在執行IO操作時,該執行緒並不會被阻塞。 

IO操作其實是有一個過程的,我們拿網路IO為例,一個網路IO主要會涉及到兩個對象,一個是調用這個IO的執行緒,另一個是系統內核。當一個read操作發生時,會經歷兩個階段。

1、等待數據準備就緒。

2、將數據從內核拷貝到調用這個IO的執行緒中。

IO模型的區別主要都在這兩個階段上面所以很重要,我們所說的同步與非同步的區別,在於第二個階段中,將數據從內核拷貝到執行緒(或進程)中,如果被阻塞了就同步,沒有被阻塞就是非同步。被阻塞了說明該階段的操作是依賴用戶執行緒的,而沒有被阻塞說明不依賴用戶執行緒,而依賴內核,所以非同步是需要作業系統內核支援的。

阻塞IO與非阻塞IO

上面我們在介紹同步IO與非同步IO的時候說到,同步與不同步的區別在IO操作的第二個階段,這節我們說的阻塞IO與非阻塞IO則是發生在IO操作第一個階段的。

阻塞IO是指當一個執行緒發起IO操作請求時,系統內核會去查看要操作的數據是否就緒,當是阻塞IO時,發現要操作是數據沒有就緒,就會一直等待下去,直到數據準備就緒;當是非阻塞IO時如果數據沒有準備好,就會返回一個標識資訊告訴調用執行緒,當前操作數據沒有準備就緒。當數據準備就緒後才會執行第一階段。

其實阻塞IO與非阻塞IO的關鍵區別在於,是等待執行,還是說立即返回一個通知標識。當數據沒有準備好時就等待執行,而當立即返回一個通知標識時,執行緒會根據標識知道現在數據是個什麼情況,如果沒有準備好,那麼執行緒會再次發起請求,知道數據準備好後立即執行。

兩種方式的組合

雖然非同步和非阻塞能夠提升I/O的性能,但是也會帶來一些額外的性能成本,例如:會增加執行緒數量,從而增加CPU的消耗,同時也會導致程式設計複雜度的上升。如果設計的不合理反而會導致性能下降,在實際設計時要分解應用場景綜合評估。

下面這個表格就列出了同步非同步與阻塞非阻塞組合起來的性能分析。

組合方式 性能分析
同步阻塞 最常用的一種用法,使用也是最簡單的,但是I/O性能一般很差,CPU大部分處於空閑狀態。
同步非阻塞

  提升I/O性能的常用手段,就是將I/O的阻塞改為非阻塞方式,尤其在網路I/O是長連接同時傳輸

數據也不很多的情況下,提升性能非常有效。

  這種方式通常能提升I/O性能,但是會增加CPU消耗,要考慮增加的I/O性能能不能補償CPU的

消耗,也就是系統的瓶頸是在I/O上還是在CPU上。

非同步阻塞

  這種方式在分散式資料庫中經常用到,例如,在一個分散式資料庫中寫一條記錄,通常會有一份是

同步阻塞的記錄,還有2~3份記錄會寫到其他機器上,這些備份記錄通常都採用非同步阻塞的方式寫I/O。

  非同步阻塞對網路I/O能夠提升效率,尤其像上面這種同時寫多份相同數據的情況。

非同步非阻塞

  這種組合方式用起來比較複雜,只有在一些非常複雜的分散式情況下用,集群之間的消息同步機制

一般用這種I/O組合方式。

  它適合約時要傳多份相同的數據到集群中不同的機器,同時數據的傳輸量雖然不大卻非常頻繁的情況。

這種網路I/O用此方式性能達到最高。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 文章會同步到我的公眾號上面,歡迎關注。