1. 程式人生 > >一位10年Java程式設計師總結進階中的你懂多執行緒和jvm優化嗎?

一位10年Java程式設計師總結進階中的你懂多執行緒和jvm優化嗎?

感謝朋友們的認可和指正。本文是有感而發,因為看過了太多坑人的部落格和書籍,感慨自己走過的彎路,不希望其他初學者被網上互相抄襲的部落格和東拼西湊的書籍浪費時間,想以一個相對巨集觀的視野來描述一個概念,力求通俗易懂,所以沒有深入太多細節,簡化了很多模型,給部分朋友造成了疑惑,說聲抱歉。也沒有配圖,都是抽時間手機碼字,打個分割線都費勁,圖呢,其實網上都有。

記得我在另外一篇答案中提到,計算機程式(不僅僅各種語言的程式碼,一切能向計算機發出指令的序列都是程式,當然包括Java虛擬機器)的努力方向:最大化利用計算機資源。多執行緒就是如此,一個CPU密集型的任務在跑,你讓IO乾等著,這不是浪費嗎?所以,這時候你啟動一個IO密集型的任務,資源利用率就提升了。當然,這是一種簡化模型,實際上一個人任務的不同階段,需要的計算機資源是不同的,如果你能合理安排多個任務的執行邏輯,資源利用率就會很大提升。

我們學習程式語言,一定不要被束縛到語言細節和規範上去,而要從計算機邏輯執行層面思考問題。因為細節和規範都是人為設定的,是大牛抽象計算機邏輯後的加工品,你囿於此,其實是在理解別人的思想,而不是理解計算機。我們常說的高層依賴於抽象而不依賴於底層,是一樣的意思。說了這麼多,想表達的就是,對技術問題,要有思考的深度,要尋根溯源,要高屋建瓴。

回到多執行緒。上面提到synchronized,必須多說幾句,這對理解鎖的本質至關重要。多執行緒和鎖,首先請大家記住一個場景:多人上廁所。

多執行緒和鎖,一個是執行緒,一個是物件。一個在私有的執行緒棧中,一個在共享的堆中。如何標識某個執行緒持有某個鎖物件?如何如何標誌某個物件被某個執行緒鎖定?很顯然,執行緒棧中開啟一片區域“棧幀”儲存物件鎖記錄,堆中物件有物件頭(物件頭主要儲存了物件的類元資料,以及物件的執行時狀態,其中就包括了鎖執行緒和GC分代等資訊。)可以標識被哪個執行緒鎖定。實際上,虛擬機器就是利用物件頭和monitor(後面講)來實現鎖的。

回到多人上廁所,人比做執行緒,廁所比做共享物件,鎖比做物件頭,monitor比做鑰匙。

synchronized鎖的是一個物件,或者是類的某個例項,或者是類本身(即常量池的Class)。synchronized內部原理是通過物件內部的一個叫做監視器(monitor)來實現的。本質又是依賴於底層的作業系統的Mutex Lock來實現的。而作業系統實現執行緒之間的切換需要從使用者態轉換到核心態,這個成本非常高,這就是為什麼synchronized效率低的原因。比如Hashtable(再次吐槽小寫t,渾身難受)和用Collections.synchronizedMap裝飾的HashMap,內部都使用了 synchronized,所以效能差,不是因為“它效能差”,而是因為“它使用的同步方式”效能差,那天人家底層重寫了效能高了你怎麼辦?很多時候,點下滑鼠進入原始碼看幾眼就知道的東西,沒必要死記硬背。

synchronized這種依賴於作業系統所實現的鎖我們稱之為“重量級鎖”。JDK中對Synchronized做的種種優化,其核心都是為了減少這種重量級鎖的使用。JDK1.6以後,為了減少獲得鎖和釋放鎖所帶來的效能消耗,提高效能,引入了大量的優化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。別被這些名詞砸暈了,這些鎖的名字很有誤導性,其實是對獲取鎖的方式的優化,不是鎖。

所謂鎖的優化,主要方向是優化獲取鎖的方式和加鎖(釋放)的方式。我不想一一解釋枯燥名詞。還是用上廁所舉例。重量級鎖可以認為是,你去上廁所,得先去管理處(人或者機器)登記並拿到鑰匙上廁所,這個過程可以認為存在一次“使用者態”到“核心態”的切換。是非常重量級的。

這裡我必須強調一下,你的目標是上廁所,不是加鎖,加鎖只是為了你更好的上廁所。執行緒也一樣,目的是為了完成某項任務。加鎖是不得以為之的。

假如一層樓就你一個人,一個廁所, 你覺得還有必要去登記嗎?要什麼自行車?直接上啊。這就是無鎖狀態;如果這層樓還有一個哥們,但他尿泡比較強悍,一天不上廁所。廁所門上有個顯示器,能顯示上次上廁所的是誰、期間有沒有其他人上廁所,那你上的時候,只要看下顯示器就知道:沒別人上過,還是我,照片都沒變,不用刷臉,此廁可直接上。這就是偏向鎖,因為“偏向你”;假如這個哥們偶爾也上一次,這次你發現廁所有別人上過,因為顯示器上有他照片,那你就得重新刷臉,好吧,那我再刷了上吧,大部分時候,裡面都沒這哥們,你可順利上廁所,這叫輕量級鎖;如果某天這哥們腹瀉(我一同事吃湖南蒸菜有過一次),那你悲劇了,你每次上的時候,不僅顯示器不是你,你想刷臉進入,發展裡面還有人。沒辦法,只能去管理處登記等待了,變成了重量級鎖。鎖升級是不會降級的。這裡,重量級鎖涉及作業系統的處理,而偏向鎖和輕量級鎖涉及CAS,硬體可以搞定,效率更高。

上述鎖狀態轉移和加鎖(解鎖不講了)是由虛擬機器(配合作業系統)完成的,我們不可見,既然是虛擬機器控制,當然就有相關引數,如是否啟用偏向鎖,我忘了引數名字,但我知道肯定有這樣的引數。如果面試我的面試官因為我不知道引數名字鄙視我,我能反懟死他。記個別人定的名字很自豪?

上面講到重量級鎖的時候,其實就是鎖競爭很激烈的時候。比如早上高峰期,廁所坑位緊缺,排隊的人很多,如果你一直等,等待的狀態就叫“自旋”,當然你可以自旋十分鐘左右後離開(虛擬機器自旋也有引數控制),因為你覺得裡面的哥們玩手機不知道啥時候結束,你有更重要的事情要幹,還不如去外面登記等通知。顯然,自旋的前提是你知道上一個哥們不會很久。多次之後,你會摸清這些人上廁所的時間後,你自旋起來就更有針對性了,這叫“適應性自旋”。

還有,鎖消除,鎖粗化,比如基本沒人用的StringBuffer、Vector,你用在某個方法中,其實根本沒必要加鎖,或者說比如連續的append,沒必要每次都加鎖,虛擬機器就會進行鎖消除或者鎖粗化處理。

上面講了這麼多,主體是執行緒和鎖物件,核心是獲取鎖的方式和鎖定的方式,還有,不加鎖或者“偽加鎖”是不是能搞定?再次強調一遍,執行緒生來是為了完成任務的,不是為了和鎖糾纏的。

多執行緒競爭鎖的時候,肯定涉及到執行緒的排隊,新來的執行緒怎麼處理,是去競爭鎖還是直接排隊?排隊中的執行緒,那些有資格競爭鎖?有資格的執行緒,那個拿到鎖(只是拿到鎖,還未執行共享區)?不管怎麼實現,這些東西是必須要考慮的。你在synchronized沒見到,是因為虛擬機器幫你處理了,涉及的佇列也是虛擬機器在維護。重量級鎖的時候,又涉及和作業系統訊號的互動。當然,要是你不用和作業系統進行如Mutex Lock這樣“重量級的”互動也能更好、更快、更好的處理同步,那你就是大牛了。

大牛當然是存在,比如李老頭。下面會開始講更加靈活的、細粒度、可定製的Lock鎖。可以認為是把synchronized加鎖的過程、鎖定的方式等流程中細節拆分出來,用靈巧的實現方式實現執行緒同步。再後面會講物件的wait、notify,執行緒的sleep,主體不一樣,思考的角度不一樣。今天先到這裡。

最後呢我想給大家分享一下我是怎麼爬出這個坑的。因為我這邊收集到了很多的學習視訊,並且也關注了幾個不錯的課堂,下面我就給大家分享一個我的學習計劃:

很多問題其實答案很簡單,但是背後的思考和邏輯不簡單,要做到知其然還要知其所以然。如果想學習Java工程化、高效能及分散式、深入淺出。效能調優、Spring,MyBatis,Netty原始碼分析的朋友可以加我的Java進階架構學習群:952124565,群裡有阿里大牛直播講解技術,以及Java大型網際網路技術的視訊免費分享給大家。