各種程序員都工作在各自的程序抽象維度,如果我們發(fā)現(xiàn)解決一件事情比較難,也許是我們面對的抽象級別還不夠高,或者引入的間接程度不夠,本文以抽象角度來剖析并發(fā)編程。

  一、機器和OS級別抽象

  (1)馮諾伊曼模型

  經(jīng)典的順序化計算模型,貌似可以保證順序化一致性,但是沒有哪個現(xiàn)代的多處理架構(gòu)會提供順序一致性,馮氏模型只是現(xiàn)代多處理器行為的模糊近似。

  這個計算模型,指令或者命令列表改變內(nèi)存變量直接契合命令編程泛型,它以顯式的算法為中心,這和聲明式編程泛型有區(qū)別。就并發(fā)編程來說,會顯著的引入時間概念和狀態(tài)依賴

  所以所謂的函數(shù)式編程可以解決其中的部分問題。

  (2)進(jìn)程和線程

  進(jìn)程抽象運行的程序,是操作系統(tǒng)資源分配的基本單位,是資源cpu,內(nèi)存,IO的綜合抽象。

  線程是進(jìn)程控制流的多重分支,它存在于進(jìn)程里,是操作系統(tǒng)調(diào)度的基本單位,線程之間同步或者異步執(zhí)行,共享進(jìn)程的內(nèi)存地址空間。

  (3)并發(fā)與并行

  并發(fā),英文單詞是concurrent,是指邏輯上同時發(fā)生,有人做過比喻,要完成吃完三個饅頭的任務(wù),一個人可以這個饅頭咬一口,那個饅頭咬一口,這樣交替進(jìn)行,最后吃完三個饅頭,這就是并發(fā),因為在三個饅頭上同時發(fā)生了吃的行為,如果只是吃完一個接著吃另一個,這就不是并發(fā)了,是排隊,三個饅頭如果分給三個人吃,這樣的任務(wù)完成形式叫并行,英文單詞是parallel.

  回到計算機概念,并發(fā)應(yīng)該是單CPU時代或者單核時代的說法,這個時候CPU要同時完成多任務(wù),只能用時間片輪轉(zhuǎn),在邏輯上同時發(fā)生,但在物理上是串行的。現(xiàn)在大多數(shù)計算機都是多核或者多CPU,那么現(xiàn)在的多任務(wù)執(zhí)行方式就是物理上并行的。

  為了從物理上支持并發(fā)編程,CPU提供了相應(yīng)的特殊指令,比如原子化的讀改寫,比較并交換。

  (4)平臺內(nèi)存模型

  在可共享內(nèi)存的多處理器體系結(jié)構(gòu)中,每個處理器都有它自己的緩存,并且周期性的與主存同步,為什么呢?因為處理器通過降低一致性來換取性能,這和CAP原理通過降低一致性來獲取伸縮性有點類似,所以大量的數(shù)據(jù)在CPU的寄存器中被計算,另外CPU和編譯器為了性能還會亂序執(zhí)行,但是CPU會提供存儲關(guān)卡指令來保證存儲的同步,各種平臺的內(nèi)存模型或者同步指令可能不同,所以這里必須介入對內(nèi)存模型的抽象,JMM就是其中之一。

  二、編程模型抽象

  (1)基于線程模型

  (2)基于Actor模型

  (3)基于STM軟件事務(wù)內(nèi)存

  ……

  Java體系是一個基于線程模型的本質(zhì)編程平臺,所以我們主要討論線程模型。

  三、并發(fā)單元抽象

  大多數(shù)并發(fā)應(yīng)用程序都是圍繞執(zhí)行任務(wù)進(jìn)行管理的,任務(wù)是抽象,離散的工作單元,所以編寫并發(fā)程序,首要工作就是提取和分解并行任務(wù)。一旦任務(wù)被抽象出來,他們就可以交給并發(fā)編程平臺去執(zhí)行,同時在任務(wù)抽象還有另一個重要抽象,那就是生命周期,一個任務(wù)的開始,結(jié)束,返回結(jié)果,都是生命周期中重要的階段。那么編程平臺必須提供有效安全的管理任務(wù)生命周期的API.

  四、線程模型

  線程模型是Java的本質(zhì)模型,它無所不在,所以Java開發(fā)必須搞清楚底層線程調(diào)度細(xì)節(jié),不搞清楚當(dāng)然就會有struts1,struts2的原理搞不清楚的基本災(zāi)難(比如在struts2的action中塞入狀態(tài),把struts2的action配成單例)。

  用線程來抽象并發(fā)編程,是比較低級別的抽象,所以難度就大一些,難度級別會根據(jù)我們的任務(wù)特點有以下幾個類別

  (1)任務(wù)非常獨立,不共享,這是最理想的情況,編程壓力為0.

  (2)共享數(shù)據(jù),壓力開始增大,必須引入鎖,Volatile變量,問題有活躍度和性能危險。

  (3)狀態(tài)依賴,壓力再度增大,這時候我們基本上都是求助jdk 提供的同步工具。

  五、任務(wù)執(zhí)行

  任務(wù)是一個抽象體,如果被抽象了出來,下一步就是交給編程平臺去執(zhí)行,在Java中,描述任務(wù)的一個基本接口是Runnable,可是這個抽象太有限了,它不能返回值和拋受檢查異常,所以Jdk5.0有另外一個高級抽象Callable.

  任務(wù)的執(zhí)行在Jdk中也是一個底級別的Thread,線程有好處,但是大量線程就有大大的壞處,所以如果任務(wù)量很多我們并不能就創(chuàng)建大量的線程去服務(wù)這些任務(wù),那么Jdk5.0在任務(wù)執(zhí)行上做了抽象,將任務(wù)和任務(wù)執(zhí)行隔離在接口背后,這樣我們就可以引入比如線程池的技術(shù)來優(yōu)化執(zhí)行,優(yōu)化線程的創(chuàng)建。

  任務(wù)是有生命周期的,所以Jdk5.0提供了Future這個對象來描述對象的生命周期,通過這個future可以取到任務(wù)的結(jié)果甚至取消任務(wù)。

  六、鎖

  當(dāng)然任務(wù)之間共享了數(shù)據(jù),那么要保證數(shù)據(jù)的安全,必須提供一個鎖機制來協(xié)調(diào)狀態(tài),鎖讓數(shù)據(jù)訪問原子,但是引入了串行化,降低了并發(fā)度,鎖是降低程序伸縮性的原罪,鎖是引入上下文切換的主要原罪,鎖是引入死鎖,活鎖,優(yōu)先級倒置的絕對原罪,但是又不能沒有鎖,在Java中,鎖是一個對象,鎖提供原子和內(nèi)存可見性,Volatile變量提供內(nèi)存可見性不提供原子,原子變量提供可見性和原子,通過原子變量可以構(gòu)建無鎖算法和無鎖數(shù)據(jù)結(jié)構(gòu),但是這需要高高手才可以辦到。