這篇文章是Java併發程式設計思想系列的第一篇,主要從理解Java併發程式設計歷史的原因和Java併發演進過程兩部分,以極簡地回溯併發程式設計的歷史,幫助大家從歷史這個角度去了解一門語言一個特性的演進。對歷史理解的越多,思考的越多,未來的方向就會更加堅定。
我是誰?從哪來?到哪去?——柏拉圖
一、為什麼瞭解併發程式設計歷史
沒有一個新事物一出現就是完美的。回溯Java併發演進的歷史,既可以從巨集觀的角度瞭解世界上正在發生的變化[知乎],又可以讓我們真正的理解當時設計的背後邏輯和歷史原因,學習前人決策的智慧,指導我們的工作和生活。然而,Java語言建立在硬體和作業系統之上的,脫離了硬體和作業系統,單單去回溯Java併發歷史,則無法看清楚底層的邏輯。
二、併發的演進
2.1 併發的產生
綜觀計算機歷史,作業系統與計算機硬體的發展息息相關[維基]。計算機的發展經歷了4個階段,電子管計算機(1945-1955)、電晶體計算機(1955-1965)、積體電路計算機(1965-1980)、大規模積體電路計算機(1980-至今)。因此,伴隨著計算機硬體的更新換代,作業系統也經歷了4個階段,分別是手工操作(50年代早期)、單道批處理系統(50年代)、多道批處理系統(60年代初)、分時系統(60年代中)[作業系統發展歷史]。
電晶體的發明後,計算機的可靠性提高了一個層級。由於當時機器非常昂貴,人們期望計算機可以長時間執行[MOS]。單道批處理系統支援把一系列的指令預先寫下來,形成一個清單,一次性的交給計算機,這樣計算機就可以連續不斷讀取指令執行相應的操作[海子],通過這種方式提高了計算機的利用率。
然而,由於在單道批處理系統中同時只能執行一個任務,任務在輸入輸出時,CPU是空閒;反之在計算時,輸入輸出裝置是空閒的。隨著積體電路晶片發明和普及,為了更加使用計算機資源,誕生了軟體相容的第三代多道批處理系統,支援多個程式同時進入記憶體並交替在CPU中執行,共享系統中的軟硬體資源。解決了上一代系統一個程式執行時,CPU與外設交替空閒和忙碌的問題,再一次提高了CPU和外設的利用率。
[圖摘自:bilibili作業系統的發展和分類]
不過人類對計算機的效率、易用性上的追求上是無止境的,主要體現在兩點:
1. 人機互動:對於第三代系統而言,一個程式從提交到運算結果取回往往需要幾個小時,有時候會由於一個小錯誤導致編譯失敗浪費很多時間,因此使用者希望可以獨佔式的使用計算機,用來除錯程式,修改錯誤。
2. 提高計算機使用效率:當時計算機還十分昂貴,一臺計算機需要同時供多個使用者共享使用,提高計算機的利用率。
此時,分時作業系統就應運而生了,分時作業系統引入了時間片的概念,把CPU時間按一定的時間間隔,採用輪轉執行的方式輪流切換給各個終端使用者的程式使用。由於時間間隔很短(linux根據程式的nice值決定如何分配時間片,一般的時間片在ms級別),每個使用者就感覺像獨佔計算機在使用。
[圖摘自:bilibili作業系統的發展和分類]
人們為了同時處理多個任務,從第三代多道批處理系統開始,引入了程式。每個程式都對應一塊自己的記憶體空間,不同程式之間互不干擾,同時程式可以儲存程式每個時刻的狀態,這樣就為程式切換提供的可能。從微觀角度看同一時刻只有一個程式在使用CPU資源(單核CPU);從巨集觀角度看有多個任務在同時在執行,這就讓併發成為可能[海子]。
2.2 執行緒的產生
程式提高了CPU的利用率,但是由於一個程式在一個時間段內只能做一件事情,所以存在一些明顯的不足:
1. 不支援同一時間進行多個事情:如果想同時幹兩件事或多件事,程式就無能為力了
2. 程式會被阻塞,無法及時響應:如果程式在執行過程中阻塞了,如等待輸入,整個程式就會掛起,無法繼續執行。
當然有些人表示,可以把多個事情拆分到多個任務。bingo!這就是執行緒最初的思想。不過每個程式都會分配單獨的記憶體空間,這種方式會佔用更多的資源。所以睿智的前人就想到,能夠採用孫悟空的分身術,讓同一個程式下的執行緒共同享有程式佔有的資源和地址空間。簡單的理解:程式屬於在處理器這一層上提供的抽象;執行緒則屬於在程式這個層次上再提供了一層併發的抽象。那麼提個問題:執行緒還可以再細分嗎?
2.3 Java併發程式設計的演進
好啦,鋪墊了這麼多,終於要說到我們今天的主角,Java的併發程式設計的歷史了。Java併發的演進與計算機系統的演進有著相似性,循著歷史的軌跡,我們可以瞭解到前人是如何在未知的道路上苦苦探索,通過學習前人深邃的思想,來指導我們的工作。
[圖摘自:Java併發程式設計通識]
Java誕生在1990年Sun公司一個內部專案,當時硬體領域出現了價格低廉的單片式計算機系統,使用它可以大幅度提升消費類電子產品(如電視機頂盒、麵包烤箱、行動電話等)的智慧化程度。Sun公司為了搶佔市場先機,在1991年成立了一個稱為Green的專案小組,James Gosling等人期望使用一種新語言來解決這類程式跨平臺執行問題,於是Java的前身Oak(橡樹)語言誕生了。(你看,有很多人抱怨工作挑戰太小,無法提升自己,但真正的創新還是要在工作基礎上進行延伸)。下圖是James Gosling,感謝老爺子養活了這麼多java人。
1996年JDK1.0版本釋出,Java的目標是write once, run anywhere,由於站在作業系統這個巨人的肩膀上,因此1.0版本就提出了Java語言的記憶體模型,並確定了執行緒模型以及實現,如Thread、Runnable。當時Java在語言層面支援了多執行緒,這是一項非常大膽的創舉,但是好事多磨,從1997年就在Java記憶體模型規範中發現了幾處嚴重的缺陷,這些缺陷造成執行的結果出現混亂,例如:被final修飾的常量值會發生更改,這些缺陷經過了很長一段時間的詬病。
每一次技術的革新總離不開硬體的發展,隨著多核架構的出現,雖然Java記憶體模型改造工程難度之大超出了想象,但是Java 的設計者們還是決定重新修訂 Java 的記憶體模型, 經過了長達 3 年的激烈討論,時間線來到了2004年9月,JDK1.5釋出,並正式更名為5.0(請記住這一個里程碑式的版本吧)。這個版本正式釋出了兩個重大的規範:JSR133和JSR166。JSR-133規範,即Java記憶體模型與執行緒規範。而JSR166的貢獻是引入了java.util.concurrent包,提到concurrent包,我想Doug Lea大神大家一定不會陌生。感謝為Java人提供了這麼易用的併發工具包。
在推出了JDK5.0後,Java反對的聲音越來越少了,但是一個有生命力的語言不會止步於此。隨著大規模資料處理的出現,2003年和2004年,Google公司在國際會議上分別發表了兩篇關於Google分散式檔案系統和MapReduce的論文,公佈了Google的GFS和MapReduce的基本原理和主要設計思想。2011年,在JDK7中進一步完善了併發流程控制功能,引入了fork-join框架。
Java從誕生到現在已經有二十年,那麼Java 的未來會怎樣?我想這新的一頁篇章一定有你揮毫書畫的風采。
[圖摘自:部落格園]
三、思想和本質
縱觀,計算機和Java併發的演進歷程,本質是人類壓榨計算機運算能力的歷史,也是人類不斷探索追求極致效能的歷史,而摩爾定律和 Amdahl定律的更替代表了近年來硬體發展從追求處理器頻率到追求多核心並行處理的發展過程[深入理解java虛擬機器]。
1. Amdahl 定律通過系統中的並行化與序列化的比重來描述多處理器系統能獲得的運算加速能力。
2. 摩爾定律則用於描述處理器電晶體數量與執行效率之間的發展關係。
本文作者: 葛一凡
分享是快樂的,也見證了個人成長曆程,文章大多都是工作經驗總結以及平時學習積累,基於自身認知不足之處在所難免,也請大家指正,共同進步。
注:所有非本人內容均以[]標註,踐行原創,踐行知識源頭,從我做起。
參考
-
鄒恆明. 計算機的心智 作業系統之哲學原理. 機械工業出版社
-
[荷] Andrew S. Tanenbaum. 現代作業系統[MOS](原書第4版). 機械工業出版社