學習併發編程之初好像就一直對這個問題含混不清,在閱讀《Java8實戰》以及網絡資源的時候對這個問題有了更進一步的認識,特此梳理一下java
這裏引用Java8實戰中的一張圖片來加以說明編程
可能從上圖簡單來看,併發是單處理器核心多任務的交替執行,並行是多任務多處理器核心的同時執行,因爲這個問題並無被蓋棺定論規範化,致使可能不一樣的人有不一樣的理解,我也並不能給出一個嚴格意義上準確的定義,可是我綜合他人的觀點給出的本身的定義以下,並行是併發的一種表現形式,併發只強調兩個任務的生命週期存在交集,即對用上面的任務1開始到結束的過程當中,若是任務2也開始了,那麼咱們就認爲任務1和任務2是併發的。可是今天想梳理的並非嚴格意義上的區分這兩個關聯緊密的概念,而是討論這二者可以給咱們的程序帶來什麼?tomcat
併發更加側重於壓榨單個CPU的性能,以提升CPU利用率爲目的,對於一串任務(task1,task2,task3...)高併發並不能加快這些任務整體完成的時間,甚至因爲線程切換還會延長任務整體完成的時間,因此它並非以提升總體響應速率爲目的的,而並行它使得多個任務(若是不相干,簡化討論,避免多核之間的一致性要求)能夠在多個處理器核中獲得真正的同時處理,而這個時候對於一系列的不相干任務來講,利用並行計算,就能大大縮短總體的響應時間安全
答案是顯然的,不能,並且因爲線程切換帶來的資源開銷,單線程併發還會延長整個任務的處理時間?網絡
有必要,並且很是有必要,首先咱們假定有四個任務1,2,3,4以下,每一個任務的執行耗時1個單位時間,若是按照單線程串行的執行方式,它應該是這樣的多線程
對於task1來講,它還能接收,畢竟執行1個單位時間它就拿到了它想要的結果,可是對於後面的task來講就不滿意了,特別是task4來講,執行task4的耗時爲1個單位時間,可是它須要等4個單位時間才能拿到結果,若是在多線程狀況下,它是如何的呢?假設每一個task都另起了一個線程,且不考慮操做系統任務調度耗時等等,如今的處理狀況是這樣的併發
假如理想情況下,每一個任務被切割得足夠小,那麼最終每一個任務幾乎是同時開始同時結束,那麼每一個task的用時就是總耗時的平均值也就是2.5,這下task4總算開心了,它不用等那麼久了。高併發
可是實際問題中,不可能把任務無限切分,操做系統的線程調度也是耗時操做,那麼上面的結論就不必定那麼可靠了,甚至可能每一個時間都超過3了,那還不如串行呢,至少task1和task2爽了,那爲何還須要併發呢?性能
由於實際情況下,每一個任務的執行速度也不可能徹底相等,每一個任務執行的速度有快有慢,咱們如今假設task1執行用時須要1000個單位時間,若是在串行狀況下,task1後面的全部任務都會被task1所拖累,須要等待的時間爲1000加,而此時的併發執行策略中,雖然因爲系統調度等等開銷,task2,3,4仍然能夠以一個與以前速度相差無幾的時間響應,task1帶來的惡劣影響也單單隻影響到了本身。咱們上面的策略也就相似於tomcat對於請求的處理策略,針對每一個請求都另起一個線程processor來處理。學習
有必要,一般一個大任務是由多個小任務組合而成,若是按照CPU密集型和I/O密集型來劃分任務類型的話,對於CPU密集型任務來講,不管咱們再怎麼多線程瘋狂操做也好,在單核處理器中,最終都須要依靠處理器來作運算,多線程的開銷無疑延長了整個任務的處理時間,可是在I/O密集型任務狀況下(包括磁盤IO,網絡IO),假設你發起了10次網絡IO,發起了10個不一樣的RPC調用,無疑多線程的方式可以讓你同時發起多個請求,多個請求同時等待被調用方的響應以及網絡延遲,不然你就只能按照串行的方式,每一個請求都須要等一個時延,而後再處理下一個請求,但其實這樣的併發其實更加相似於並行,由於你發起的遠程調用是另外一個處理器去幫你處理的,咱們所作的只不過是利用併發再一個請求傻等着的過程當中又發起了另外一個請求罷了
並行的好處是顯而易見的,多個處理器幹活確定是快於一我的幹活的,對於上面討論的狀況,若是在多核心的處理器下,併發以後可能整個處理過程就是並行的,小的任務能夠在多個處理器核心中同時運行,在這裏也不太過多討論併發安全的問題,主要討論如何高效並行
在tomcat中想要並行很簡單,你併發就好,若是你有多個處理器核心它天然會並行執行,可能並不太須要咱們對整個處理過程進行並行處理,關注更多的是不一樣請求之間的並行,可是在一些場景下,可能就須要咱們關注整個任務自己的並行,這時候並行就不那麼容易,假設你要計算1-1000000000的和,你固然能夠選擇併發執行,本身分割每一個處理器計算多少到多少的和,而後自行彙總結果,就想下面的代碼同樣
public class ConcurrentVsParallel { public static void main(String[] args) throws ExecutionException, InterruptedException { //串行 long sum=0; long time1=System.currentTimeMillis(); for (long i = 1; i <= 10000000000L; i++) { sum+=i; } System.out.println("串行計算結果爲:"+sum); System.out.println("串行耗時:"+(System.currentTimeMillis()-time1)); long time2= System.currentTimeMillis(); long res = concurrentCal(10000000000L); System.out.println("計算結果爲:"+res); System.out.println("並行耗時爲:"+(System.currentTimeMillis()-time2)); } public static long concurrentCal(final long n) throws ExecutionException, InterruptedException { //4等分來處理 ExecutorService executor = Executors.newFixedThreadPool(4); long quarter=n/4; long allSum=0; Future[] parts = new Future[4]; for (int i = 0; i < 4L; i++) { final int temp=i; Future<Long> partSum = executor.submit(() -> { long sum = 0; for (long j = temp * quarter + 1; j <= (temp + 1) * quarter; j++) { sum += j; } return sum; }); parts[i]=partSum; } for (int i = 0; i < parts.length; i++) { allSum+=(long)parts[i].get(); } return allSum; } }
輸出結果以下:
串行計算結果爲:-5340232216128654848 串行耗時:4617 計算結果爲:-5340232216128654848 並行耗時爲:1847
上述的代碼可以實現咱們既定的目標,可是存在着可讀性和可拓展性的問題,性能也存在着問題,若是須要對(2-n)求和呢,很簡單,給咱們的代碼加入一個start便可,可是若是須要對(2-n)中全部的偶數求和呢?豈不是又須要改代碼,更加嚴重的問題是任務規模的劃分是定下來的,固然你也能夠再添加一個參數設置任務規模的劃分,可是上述這些操做都會致使代碼的膨脹和難以維護,利用java8的Stream能夠作以下簡單實現
long time3=System.currentTimeMillis(); long res = LongStream.rangeClosed(1, 10000000000L).parallel().sum(); System.out.println("stream計算結果爲:"+res); System.out.println("stream耗時爲:"+(System.currentTimeMillis()-time3));
結果以下:
串行計算結果爲:-5340232216128654848 串行耗時:4631 stream計算結果爲:-5340232216128654848 stream耗時爲:3605
雖然這裏的耗時可能比不過咱們直接手動劃分,併發的方式去進行計算,可是這裏的代碼可讀性以及簡潔讀是很是好的,誠然這個結果也受限於我僅僅只有四核的垃圾筆記本,不管如何經過Stream的方式,java在並行方面的能力也是很是強的
《Java8實戰》