1. 程式人生 > >深入理解Java虛擬機器學習筆記3-執行緒安全和鎖優化

深入理解Java虛擬機器學習筆記3-執行緒安全和鎖優化

併發處理是壓榨計算機運算能力最有力的工具。

1.執行緒安全

當多個執行緒訪問一個物件時,如果不用考慮這些執行緒執行時環境下排程和交替執行,也不需要進行額外的同步,或者在呼叫方進行任何其他的協調操作,呼叫這個物件的行為都可以獲取正確的結果,那麼這個物件是執行緒安全的。

2.Java語言中的執行緒安全

先決條件:多個執行緒之間存在共享資料訪問這個前提。

按照執行緒安全"安全程度"由強至弱來排序,可以將java語言中各種操作共享的資料分為5類:不可變、絕對執行緒安全、相對執行緒安全、執行緒相容和執行緒對立

2.1 不可變

不可變物件一定是執行緒安全的,無論是物件的方法實現還是方法的呼叫者,都不需要再採取任何的執行緒安全措施。

例:

共享資料是基本資料型別,定義時候final修飾,它是不可變的。

共享資料是物件,就需要保證物件行為不會對其狀態產生任何影響才行,才能被認為不可變,就像String物件,因為通過把String物件中帶狀態的變數 char[],通過final修飾為不可變變數,這個物件在建構函式結束之後,就是不可變的,所有呼叫String的subString(),replace(),Contact()這些方法都不會影響原來的值,只會返回一個新構造的字串物件。

注意:保證物件行為不影響自己狀態的途徑很多,最簡單就是把物件中帶有狀態的變數都宣告為final.這樣構造完的物件,都是不可變的。

2.2 絕對執行緒安全

這種執行緒安全的定義就是要求,不管執行時環境如何,呼叫者都不需要任何額外的同步措施。Java API中標註自己是執行緒安全的類,大多數都是不是絕對的執行緒安全。

例:

Java.util.Vector是一個執行緒安全的容器,他的方法add()、get() 、size()這類方法都是被synchronized修飾的,這樣效率低,但是確實是安全的。但是,即使它所有的方法都被修飾成同步的,也不意味著呼叫時候不需要其他同步手段了。

多執行緒操作同一個Vector進行移除、獲取也會出現下表越界等異常,需要操作Vector物件時額外加入同步,保證安全。

2.3 相對絕對安全

這種執行緒安全就是通常意義上的執行緒安全,它保證對這個物件單獨的操作是執行緒安全的,不需要做額外保證措施,但是對於一些特殊順序的連續呼叫,就需要在呼叫端使用額外的同步手段來保證呼叫的正確性。例如2.2描述的。

2.4執行緒相容

本身不安全,但是可以通過在呼叫端使用同步手段來保證物件在併發環境中可以安全的使用,平常說的一個類是否是執行緒安全,絕大多數是指這種情況。就像是Vector HashTable對應集合類ArrayList HashMap

2.5執行緒對立

執行緒對立是指無論呼叫端是否採取同步,都無法在多執行緒環境下併發使用程式碼。Java語言天生就是多執行緒特效,執行緒對立這種排斥多執行緒的程式碼很少出現,而且通常是有害的,應當儘量避免。

例:Thread類的suspend()和resume()方法,如果有兩個執行緒同時持有一個執行緒物件,一個嘗試中斷執行緒,另外一個嘗試去恢復執行緒,如果併發進行,無論呼叫是否做了同步,目標執行緒都存在死鎖風險的,如果suspend中斷的執行緒就是即將執行resume那個執行緒,那就是肯定要產生死鎖的。

3.執行緒安全的實現方法

編碼實現或虛擬機器提供同步和鎖機制,去實現執行緒安全。

3.1同步互斥

同步:多個執行緒併發訪問資料時,保證共享資料在同一個時刻只被一個執行緒使用。互斥是實現同步的一個手段,臨界區、互斥量、訊號量都是互斥實現方式。因此互斥是因,同步是果,互斥是方法,同步是結果。

java實現方式Synchronized和ReentrantLock來實現同步。效能來說,差不多,區別在於ReentranLock,除了api層面的互斥鎖(lock() unlock()方法配合try/finally語句塊來完成),另外就是語法層面的互斥鎖,最重要就是增加一些高階特性,等待可中斷、可實現公平鎖、以及鎖可以繫結多個條件。

Synchronized關鍵字原理:

synchronized關鍵字經過編譯後,會在同步塊前後形成monitorentor和monitorexit兩個位元組碼指令,這兩個位元組碼都需要一個指標型別的引數來指明鎖定和解鎖的物件。

synchornized同步塊對同一個執行緒來說是可重入的,不會出現自己把自己鎖死。同步塊在已經進入執行緒執行完之前,會阻塞後面其他執行緒的進入。

阻塞或喚起執行緒,需要os,需要從使用者態切換到核心態,這種狀態轉換需要耗費許多處理器的時間,因此同步是一個重量級的操作,確定必要的情況下才需要進行同步,虛擬機器也做了一些優化,在通知作業系統阻塞執行緒之前加入了一段自旋等待過程,避免頻繁切入到核心態中。

synchronized的鎖是非公平的,ReentrantLock預設情況下是非公平的。

ReentrantLock鎖繫結多個條件是指,一個鎖可以繫結多個條件,不用通過增加鎖,增加繫結條件,synchronized中鎖物件的wait(),notify()notifyAll()只可以實現預設隱含條件。

3.2非同步阻塞

同步阻塞是一種悲觀併發策略,總是認為只要不去做正確同步策略,就肯定出問題,都需要加鎖。樂觀鎖認為沒有執行緒競爭共享資料,如果有競爭,就產生了衝突,再採取補償措施,就是不斷重試,直到成功,不需要把執行緒掛起,因此成為非阻塞同步。

樂觀併發策略依賴 硬體指令集的發展,確保操作和衝突檢測是具有原子性的。CAS指令需要3個運算元,分佈是記憶體位置V、舊的預期值A、新值B.CAS指令執行時,當且僅僅當V符合舊的預期值A時,處理器才會用新值更新V值,否則不更新,但是無論是否更新V的值,都會返回V的舊值。

JAVA裡面樂觀鎖 Atomic類 如AtomicInteger..

3.3無同步方案

如果一個方法本身不涉及共享資料你,那就無需同步,有一些程式碼天生就是執行緒安全的。

a.可重入程式碼

不依賴與堆上資料和共用系統資源,用到的引數都是引數傳入的、不呼叫非重入的程式碼。判斷的原則就是,一個方法,返回結果是可預測的,相同輸入,必定得到相同結果,這程式碼就是執行緒安全的。

b.執行緒本地儲存

執行緒所需的資料,都是執行緒本地儲存,不和其他程式碼共享,是僅在一個執行緒中。

web的一個請求對應一個伺服器執行緒,解決執行緒安全

執行緒獨享ThrealLocal儲存。每個執行緒的Thread物件都有一個ThreadLocalMap物件,這個物件儲存了一組以ThreadLoca.threadLocalHashCode為鍵,以本地執行緒變數為值的K-V值對。ThrealLocal物件就是當前執行緒的ThrealLocalMap的訪問入口,每個ThrealLocal物件都包含了一個獨一無二的threadLocalHashCode值,使用這個值作為key就可以找到執行緒中儲存在本地執行緒變數K-V值對。

4.鎖優化

4.1自旋鎖與自適應自旋

互斥對效能最大的影響是阻塞的實現,掛起執行緒和恢復執行緒的操作都需要轉入核心態完成,這些操作給系統併發效能帶來很大的壓力。通過自旋等待鎖,避免直接執行緒切換的開銷,自旋超過限定次數仍然沒有成功,才會使用傳統方式掛起執行緒。

自適應自旋鎖,可以根據程式執行和效能監控不斷完善,確定是否自旋,自旋次數,以達到更好虛擬機器對程式鎖狀況預測,做出調整目的。

4.2鎖消除

虛擬機器在即時編譯器執行時候,對一些程式碼上要求同步,但是檢測到不可能存在共享資料競爭的鎖進行消除。主要依據來源於逃逸分析的資料支援。執行緒私有的,同步鎖無需執行。

例;虛擬機器會對字串拼接,直接優化為StringBuilder拼接,append裡面會有同步程式碼塊,有時候會更加變數作用域判斷是否有必要同步,是否需要進行鎖消除。

4.3鎖粗化

寫程式碼時候,總是推薦同步程式碼塊的範圍儘量小,只在共享資料的實際作用於下才進行同步,這樣為了使得需要同步的運算元量儘可能變小,如果存在鎖競爭,那等待鎖的執行緒也能儘可能的拿到鎖。

JVM則會根據實際情況優化,對一串零碎的操作都對一個物件加鎖操作,會粗化到整個操作序列外部,就行String.append方法拼接字串,只會加鎖一次。因為就算沒有執行緒競爭,頻繁的進行互斥鎖的同步操作,也會導致效能損耗。例如加鎖操作出現在一個迴圈體中。

4.4輕量級鎖

輕量級鎖是相對而言的,Synchronized是重量級鎖,它的本意是在沒有多執行緒競爭的前提下,減少傳統的重量級鎖使用作業系統互斥量產生的效能損耗。

4.5偏向鎖

消除資料在無競爭情況想同步,進一步提高程式的執行效能。輕量級鎖是在無競爭的情況下使用CAS操作去消除同步使用的互斥量,那偏向鎖就是在無競爭情況下把整個同步消除掉,連CAS操作都不要了。

推薦兩篇比較好是介紹:輕量級鎖、重量級鎖、偏向鎖、自適應自旋鎖的文章: