昨天頭兒給的學習文檔我還沒看完,頭兒說:“MongoDB光會簡單的添刪改查什么的不行,要深入了解,你們連$set和$inc使用場景都分不清。”
確實,學習過一年多SQL,確實對學習MongoDB有點影響。
不過,今天數據庫的事情先翻過去,因為我在學習文檔中還看到了另外一個加大加粗的標題——異步編程。
Java在Java8之前貌似(因為我也剛學,所以不對還請各位前輩指正)沒有真正實現異步編程的方法,當時異步編程會使用回調或者使用其他的框架(如Netty和Guava)來實現。后來Java8借鑒了很多框架的思想,可以借助JDK原生的CompletableFuture來實現異步操作,而且用Lambda表達式來寫匿名內部類大大簡化了代碼量。
那么,異步編程到底是用來干什么的呢?
試想這樣的場景——老爸有倆孩子:小紅和小明。老爸想喝酒了,他讓小紅去買酒,小紅出去了。然后老爸突然想吸煙了,於是老爸讓小明去買煙。在面對對象的思想中,一般會把買東西,然后買回來這件事作為一個方法,如果按照順序結構或者使用多線程同步的話,小明想去買煙就必須等小紅這個買東西的操作進行完。這樣無疑增加了時間的開銷。異步就是為了解決這樣的問題。你可以分別給小紅小明下達指令,讓他們去買東西,然后你就可以自己做自己的事,等他們買回來的時候接收結果就可以了。
所以,為了盡快學習異步操作,我使用Java8的方法編寫了這樣一段代碼來幫助了解:
1 package com.liumaowu; 2 3 import java.util.concurrent.CompletableFuture; 4 import java.util.concurrent.ExecutorService; 5 import java.util.concurrent.Executors; 6 7 public class TestFuture { 8 public static void main(String[] args) { 9 //兩個線程的線程池 10 ExecutorService executor= Executors.newFixedThreadPool(2); 11 //小紅買酒任務,這里的future2代表的是小紅未來發生的操作,返回小紅買東西這個操作的結果 12 CompletableFuture<String> future2 = CompletableFuture.supplyAsync(()-> { 13 System.out.println("爸:小紅你去買瓶酒!"); 14 try { 15 System.out.println("小紅出去買酒了,女孩子跑的比較慢,估計5s后才會回來..."); 16 Thread.sleep(5000); 17 return "我買回來了!"; 18 } catch (InterruptedException e) { 19 System.err.println("小紅路上遭遇了不測"); 20 return "來世再見!"; 21 } 22 },executor); 23 24 //小明買煙任務,這里的future1代表的是小明未來買東西會發生的事,返回值是小明買東西的結果
25 CompletableFuture<String> future1 = CompletableFuture.supplyAsync(()->{ 26 System.out.println("爸:小明你去買包煙!"); 27 try { 28 System.out.println("小明出去買煙了,可能要3s后回來..."); 29 Thread.sleep(3000); 30 return "我買回來了!"; 31 } catch (InterruptedException e) { 32 System.out.println("小明路上遭遇了不測!"); 33 return "這是我托人帶來的口信,我已經不在了。"; 34 } 35 },executor); 36 37 //獲取小紅買酒結果,從小紅的操作中獲取結果,把結果打印 38 future2.thenAccept((e)->{System.out.println("小紅說:"+e);}); 39 //獲取小明買煙的結果 40 future1.thenAccept((e)->{System.out.println("小明說:"+e);}); 41 42 System.out.println("爸:loading......"); 43 System.out.println("爸:我覺得無聊甚至去了趟廁所。"); 44 System.out.println("爸:loading......"); 45 } 46 }
這個運行結果如下:
爸:小紅你去買瓶酒! 小紅出去買酒了,女孩子跑的比較慢,估計5s后才會回來... 爸:小明你去買包煙! 小明出去買煙了,可能要3s后回來... 爸:loading...... 爸:我覺得無聊甚至去了趟廁所。 爸:loading...... 小明說:我買回來了! 小紅說:我買回來了!
因為sleep時間調的挺大,所以可以很明顯的看到小明買回來的結果是在等待之后一會兒才跳出來,小紅買回來的結果也等待了一會兒才跳出來。因為是先讓小紅買,再讓小明出去的,但是小明的結果先回來,這說明什么時候取到結果取決於操作的時間長度。
之后我把取futrue的結果的操作放到了老爸等待語句的下面,如下:
1 /* 2 上面的代碼不動 3 */ 4 System.out.println("爸:loading......"); 5 System.out.println("爸:我覺得無聊甚至去了趟廁所。"); 6 System.out.println("爸:loading......"); 7 //獲取小紅買酒結果 8 future2.thenAccept((e)->{System.out.println("小紅說:"+e);}); 9 //獲取小明買煙的結果 10 future1.thenAccept((e)->{System.out.println("小明說:"+e);});
多次運行之后結果就比較有意思了:
結果1:
結果2:
結果3
這種解決異步的方式還是使用到了多線程,在獲取結果之前,操作的順序取決於線程操作到了哪一步,比如結果2,因為小紅線程啟動的慢了,所以小明先出去了。但是如果先取結果的話,會確保這個線程已經啟動了,所以老爸loading始終顯示在老爸發出買東西命令之后。這也算先強制線程啟動,再進行主操作。
(補充:可能因為更換了電腦,性能有所提高,導致再次試驗這段代碼的時候只出現結果一。但是爸爸的loading代碼出現在小明執行動作的前面已經可以解釋,這段異步代碼的執行時間取決於線程的啟動速度和執行。)
在看了異步之后,不可避免地會把同步/異步/多線程弄混淆,那么我們再來總結下幾個東西的區別:
一般情況:順序結構,必須等待前面的操作完成(兩個人說話,a把所有話說完,b才能繼續說)
並發:同一時間段處理多個任務的能力(兩人說話,支持你一言我一語的交流,兩人在一個時間段內都有說話,是基於時間段內的同時發生)
並發又有同步和互斥
互斥:不能同時使用臨界資源(有一個共享資源--話筒,兩人必須用話筒說話,但同時只能有一個人用這個話筒,保證了只有一個人在說話)
同步:前一個處理的結果作為下一個處理的資源。大多數情況下,同步已經實現了互斥。(兩人你一言我一語的交流,我必須知道你說了啥我才能接上你的話)
並行:同一時刻處理多個任務的能力(兩人合唱,同時出聲)
異步:不用等待一個結果出來,可以繼續其他操作(兩個人不說話了,寄信,a把信拿到郵局就不用管了,回家可以想干嘛就干嘛,等b回信到了,取郵局接收一下結果--b的回信就可以了)
多線程:如果說同步和異步是對如何處理事情的要求,那么多線程就是實現這些要求的方法。