Java並發編程常識
- 2021 年 1 月 26 日
- 筆記
這是why的第 85 篇原創文章

寫中間件經常要做兩件事:
1.延遲載入,在記憶體快取已載入項。 2.統計調用次數,攔截並發量。
就這麼個小功能,團隊里的人十有八九寫錯。
上面這句話不是我說的,是梁飛在他的部落格裡面說的。

梁飛是誰?
據網上的公開資料,梁飛,花名虛極。
2009 年加入阿里巴巴,負責中間件的開發,Dubbo 開源分散式服務框架作者,HTTL 開源模板引擎作者。
2012 年加入天貓,負責手機天貓 APP 的技術團隊,見證了天貓雙 11 無線化全過程。
熱衷參與開源社區建設,傳播服務化、SOA、框架設計、移動應用等架構設計理念。
下面這段是我在他的個人部落格裡面找到的:
//www.iteye.com/blog/javatar-287026
上周五去杭州面試,早上飛過去,晚上飛回來,有點匆忙,飛機晚點,到那邊的時候都快下午一點了,面試了一個小時左右,主要是筆試沒做了,省了不少時間。
面試完後,跟著幾個熟人轉了一圈,學習交流氣氛都很好。
周二收到了Offer,看來HR效率很高,贊一個,定的是1月8號入職。
這兩天忙於提交辭職申請,就要離開奮鬥了近三年的公司,還是有點不舍,公司給了我很多發展機會,領導們對我也特別好,真的很感謝他們。
只是現在新的公司發展機遇更好,我不想放棄,希望在新的崗位上能做得更好,呵呵,流水帳記到這裡。
這篇部落格,發佈於 2008 年 12 月 24 日。
他說的上周五,也就是 2008 年 12 月 19 日。一天之內,從深圳飛到杭州,面試一小時,又從杭州飛回深圳,4 天之後,收到了阿里的 offer。
彼時,梁飛也僅從湖南科技職院軟體學院的大門走出來不到 3 年的時間。
文章裡面說的「1 月 8 號入職」,也就是 2009 年的 1 月 8 日。
從此,在阿里開啟了長達 12 年的升級打怪之路,從 P5 一路殺到了 P9。
眾所周知,阿里能到 P9 ,已經不是能力的事兒了,這屬於祖上積德。
而梁飛在 2016 年的時候就已經是 P9 了。
上面說的這麼多標籤,而他最為人熟知的標籤應該是:Dubbo 創始人。
在梁飛加入阿里的 1015 天之後,也就是 2011 年 10 月 20 日 23 點 06 分,Dubbo 在開源社區發出了第一聲啼鳴:

9 個模組,共計 669 個文件,就是日後這個一路坎坎坷坷、幾近夭折、友商續命,最終成為 Apache 頂級開源項目的雛形。
2011 年 10 月 20 日那天晚上,對於梁飛來說註定是一個回憶起來,難以忘懷的夜晚。
他一直進行了13 次提交:

終於在第二天的早上 5 點 25 分給 Dubbo 打上了第一個 tag:2.0.7。
期間還抽空通知官媒發了個微博:

而他自己,第二天的中午,也在自己的部落格上公布了這件事情:

為什麼 Dubbo 會選在這一天進行開源呢?
我想應該是為了趕上兩天之後的 Qcon 全球軟體開發者大會:

那一天,才是 Dubbo 真正意義上,站在大眾視野里,接受讚揚與嘲諷的開始。
我千辛萬苦,找到了近 10 年前,那次大會的分享 PPT,開篇第一頁:

十年過去了,白雲蒼狗,Dubbo 從阿里走了出來,已經真正的飛入尋常百姓家了。
十年間,PPT 上的數據,從總量上來看,就像雙十一的銷售額一樣,不知道翻了多少個翻。
但是轉念一想,這是 10 年前的阿里,每天 10 億次的調用,這是多少技術人一生也接觸不到的流量啊。
同時,我在 PPT 裡面也發現很多熟悉的圖片,比如這張:

左邊是現在的 Dubbo 官網,右邊是 10 年前 Qcon 大會上的 PPT。
10 年過去了,當這兩張圖片同框的時候,不知道右邊的「老圖」,有沒有一種老父親般的欣慰。
如果說上面這個圖你的感覺還不是特別的強烈,那麼再看看這個:

左邊是官網,右邊是 PPT。
10 年過去了,整體設計沒有發生一絲變化。
我不知道你看到這裡的時候想到的是什麼,於我而言,真牛逼啊。
一個 10 年前的整體設計,竟然在日新月異、風起雲湧的互聯網行業一絲不變的屹立了下來。
這是一群工程師智慧的結晶。
Dubbo 第一次面世的 PPT 有 55 頁,裡面有很多設計從十年後的今天看去也不過時。
我就不一一展示了,有興趣的朋友,在文末可以看到獲取方式。
最後,獻上一張最早的 Dubbo 團隊的合照:

Java並發編程常識
還記得本文開篇的那句話嗎?
//www.iteye.com/blog/javatar-1963774

這裡的 PPT 鏈接失效了,我歷盡千辛萬苦,搜索找到了一份。
一共 18 頁,一一展示一下。
有的地方是純知識點,有的地方是程式碼。
反正我覺得我看明白了,有必要講一下的地方。我就在圖片下面進行一個簡短的描述。
走起。








稍微解釋一下這一頁上面的東西。
這幾個命令,我在之前的文章中也用到過:
就是下面這部分:

所以,我知道這個地方是有個坑的。
如果你把命令直接粘過去,會拋出這個錯誤:
Java HotSpot(TM) 64-Bit Server VM warning: printing of assembly code is enabled; turning on DebugNonSafepoints to gain additional output

是因為你缺少了一個叫做 hsdis-amd64.dll 的文件(windows平台)。
你需要把這個文件放在 jre/bin/server 的目錄下:

然後把命令換成這個:
-XX:+UnlockDiagnosticVMOptions
-XX:+PrintAssembly
-XX:CompileCommand=print,*AtomicInteger.incrementAndGet
再次執行程式,就有彙編輸出了:

再主要說一下 CompileCommand 這個參數。
這個參數用來訂製編譯需求,比如可以指定某個方法不做 JIT 編譯,也可以只編譯指定的方法等等。
用法是這樣的:
-XX:CompileCommand=command,method[,option]
其中的 command 有下面這些選項:
exclude,跳過編譯指定的方法 compileonly,只編譯指定的方法 inline/dontinline,設置是否內聯指定方法 print,列印生成的彙編程式碼 break,JVM以debug模式運行時,在方法編譯開始處設置斷點 quiet,不列印在此命令之後、通過-XX:CompileCommand指定的編譯選項 log,記錄指定方法的編譯日誌,若未指定,則記錄所有方法的編譯日誌 其他命令,option,help
比如 PPT 中的這個用法 -XX:CompileCommand=print,*AtomicInteger.incrementAndGet
含有就是列印 AtomicInteger.incrementAndGet 方法生成的彙編程式碼。

緩衝行對齊,這個應該是一個需要掌握的知識點,屬於關於程式優化的奇技淫巧。
偶現於面試環節。
對齊的目的是為了解決偽共享(False Sharing)的發生。
不知道偽共享的朋友,建議去了解一下。
PPT 中的例子的源碼來源是:
com.google.code.yanf4j.util.LinkedTransferQueue.PaddedAtomicReference

需要注意的是,在 Java8 裡面,提供了更加優雅的解決方案:@Contented 註解。


這個也是一個非常經典的、關於單例模式的演變過程。
PPT 上一共四個單例模式的寫法,我們一個個的看。

這就是我們耳熟能詳的餓漢式單例。通過提前初始化的方式,解決了並發問題,保證了單例性。
這裡面最關鍵的就是 final static 關鍵字。
作者也用了藍色做以區分,肯定是有深意的。
來,一起背一遍類的載入過程:
載入、驗證、準備、解析、初始化。
比如下面這個例子:
public static int value =123
變數 value,只有 static 修飾,那麼該變數在準備階段被賦上初始值,即 0。
在初始化階段被賦上 123。
但是當再多一個 final 修飾的時候:
public final static int value =123
value 變數在準備階段就會被賦上 123。
所以,你想想上面的餓漢式,是不是一個道理?
而這一方面的知識點,在《深入理解Java虛擬機》一書裡面,寫的還是很清楚的。
但是餓漢式有一個明顯的劣勢,那就是不管應用程式是否使用,都會被初始化出來。
這樣並不優雅。
於是,就到了第二段程式,飽漢式。

在需要使用的時候,才進行初始化操作。
但是怎麼保證執行緒安全呢?
通過在方法上加 synchronized 關鍵字的方式來保證了執行緒安全?
但是這個玩意真的安全嗎?
這可能是面試官最喜歡問的問題之一吧。
要是你之前不知道,也許打死你也想不到還有拿到一個只初始化了一半的對象的情況。
簡單來說就是 new Singleton()
不是原子性的,所以會導致 if 條件滿足時,instance 並沒有完全初始化。
於是就引出了下面這段程式:

傳說中的雙重檢查加鎖。
而這段程式最容易被「忽視」的就是 volatile 關鍵字了。
如果沒有 volatile 關鍵字,那麼這個程式也是廢的。
還有,需要注意的是梁飛在這裡備註了一個 jdk1.5+
。
為什麼呢?
其實答案就寫在《Java並發編程實戰》的第 287 頁:

在這本書裡面,形容雙重檢查加鎖的成語是:聲名狼藉。
而且在圖片裡面可以看到:促使該模式出現的驅動力已經不復存在,並不是一種高效的優化措施。
現在,我覺得雙重檢查加鎖主要還是應用在面試環節。
明明就不是一種高效的優化措施,卻還是一個高頻考點,為什麼呢?
還不是八股文害人啊。
接下來說最後一種,也是作者推崇的方式:

這個騷操作的學名叫做「基於類初始化的單例模式解決方案」。
JVM 在 Class 被載入後,且被執行緒使用之前,也就是類的初始化階段,會去獲取一個鎖。
這個鎖可以保證多個執行緒對同一個類進行初始化時,只有一個執行緒能初始化成功。
而該方法的原理在《Java並發編程的藝術》一書的 3.8.4 章節解釋的非常清楚。
什麼?這兩本書你都沒有?
趕緊去搞一本吧,寫的還是挺好的,我以前多次推薦過。

額……
這頁我也沒看明白。
我覺得肯定是有一個上下文的,只是梁飛沒有寫在 PPT 裡面。
導致我看的雲里霧裡的。


這裡只寫到了 CountDownLatch 和 CyclicBarrier 。
其實還有一個很重要的、很常用的、工具類:Semaphore。
如果不知道,可以看看這篇文章:
如果你上面三個都不知道。
趕!緊!去!學!




列印出來,貼在電腦旁邊,朗讀並背誦本頁。
最後說一句
文中提到的兩個 PPT 和一個文件:
阿里巴巴(B2B)的服務框架探索.ppt Java並發編程常識.ppt hsdis-amd64.dll
關注公眾號【why技術】,然後在後台回復關鍵字【ppt】即可獲得。
才疏學淺,難免會有紕漏,如果你發現了錯誤的地方,可以在後台提出來,我對其加以修改。
感謝您的閱讀,我堅持原創,十分歡迎並感謝您的關注。
我是 why,一個主要寫程式碼,經常寫文章,偶爾拍影片的程式猿。
