前言
自己以前在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用此方式性能達到最高。 |
文章會同步到我的公眾號上面,歡迎關注。