要點提煉| 理解JVM之執行緒安全&鎖優化
本篇將介紹執行緒安全所涉及的概念和分類、同步實現的方式及虛擬機器的底層運作原理,以及虛擬機器為了實現高效併發所採取的一系列鎖優化措施。
- 概述
- 執行緒安全
- 鎖優化
1.概述
在要點提煉| 理解JVM之記憶體模型&執行緒中主要介紹了虛擬機器如何實現『併發』,現在的關注點是虛擬機器如何實現『高效』。
2.執行緒安全
在實現高效之前,首先需要保證併發的正確性,因此本節先介紹執行緒安全。
a.定義:當多個執行緒訪問一個物件時,如果不用考慮這些執行緒在執行時環境下的排程和交替執行,也不需要進行額外的同步,或者在呼叫方進行任何其他的協調操作,呼叫這個物件的行為都可以獲得正確的結果,那這個物件是執行緒安全的。
要求執行緒安全的程式碼都必須具備一個特徵:
程式碼本身封裝了所有必要的正確性保障手段(如互斥同步等),令呼叫者無須關心多執行緒的問題,更無須自己採取任何措施來保證多執行緒的正確呼叫。
b.分類:按照執行緒安全的程度由強至弱分成五類
- 不可變:外部的可見狀態永遠不會改變,在多個執行緒之中永遠是一致的狀態。
- 一定是執行緒安全的
- 如何實現:
- 如果共享資料是一個基本資料型別,只要在定義時用
final
關鍵字修飾; - 如果共享資料是一個物件,最簡單的方法是把物件中帶有狀態的變數都宣告為
final
。
- 如果共享資料是一個基本資料型別,只要在定義時用
- 絕對執行緒安全:完全滿足之前給出的執行緒安全的定義,即達到“不管執行時環境如何,呼叫者都不需要任何額外的同步措施”。
- 相對執行緒安全:能保證對該物件單獨的操作是執行緒安全的,在呼叫時無需做額外保障措施,但對於一些特定順序的連續呼叫,可能需要在呼叫端使用額外的同步措施來保證呼叫的正確性。
- 是通常意義上所講的執行緒安全
- 大部分的執行緒安全類都屬於這種型別,如
Vector
、HashTable
、Collections#synchronizedCollection()
包裝的集合… - 有關實現在下一小節細說。
- 執行緒相容:物件本身非執行緒安全的,但可以通過在呼叫端正確地使用同步手段來保證物件在併發環境中可以安全地使用,
- 是通常意義上所講的非執行緒安全
- Java API中大部分類都是屬於執行緒相容的,如
ArrayList
HashMap
…
- 執行緒對立:無論呼叫端是否採取了同步措施,都無法在多執行緒環境中併發使用的程式碼。
c.執行緒安全的實現
可分成兩大手段,本篇重點在虛擬機器本身
- 通過程式碼編寫實現執行緒安全
- 通過虛擬機器本身實現同步與鎖
①互斥同步(Mutual Exclusion&Synchronization)
- 含義:
- 同步:在多個執行緒併發訪問共享資料時,保證共享資料在同一個時刻只被一個執行緒使用。
- 互斥:是實現同步的一種手段,臨界區(Critical Section)、互斥量(Mutex)和訊號量(Semaphore)都是主要的互斥實現方式。
互斥是因,同步是果;互斥是方法,同步是目的。
- 屬於悲觀併發策略,即認為只要不做正確的同步措施就肯定會出現問題,因此無論共享資料是否真的會出現競爭,都要加鎖。
- 最大的問題是進行執行緒阻塞和喚醒所帶來的效能問題,也稱為阻塞同步(Blocking Synchronization)
- 手段:
- 使用
synchronized
關鍵字:
- 原理:編譯後會在同步塊的前後分別形成
monitorenter
和monitorexit
這兩個位元組碼指令,並通過一個reference
型別的引數來指明要鎖定和解鎖的物件。若明確指定了物件引數,則取該物件的reference
;否則,會根據synchronized
修飾的是例項方法還是類方法去取對應的物件例項或Class物件來作為鎖物件。 - 過程:執行
monitorenter
指令時先要嘗試獲取物件的鎖。若該物件沒被鎖定或者已被當前執行緒獲取,那麼鎖計數器+1;而在執行monitorexit
指令時,鎖計數器-1;當鎖計數器=0時,鎖就被釋放;若獲取物件鎖失敗,那當前執行緒會一直被阻塞等待,直到物件鎖被另外一個執行緒釋放為止。 - 特別注意:
synchronized
同步塊對同一條執行緒來說是可重入的,不會出現自我鎖死的問題;還有,同步塊在已進入的執行緒執行完之前,會阻塞後面其他執行緒的進入。
- 原理:編譯後會在同步塊的前後分別形成
- 使用重入鎖
ReentrantLock
:
- 相同:用法與
synchronized
很相似,且都可重入。 - 與
synchronized
的不同:
- 等待可中斷:當持有鎖的執行緒長期不釋放鎖的時候,正在等待的執行緒可以選擇放棄等待,改為處理其他事情。
- 公平鎖:多個執行緒在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖。而
synchronized
是非公平的,即在鎖被釋放時,任何一個等待鎖的執行緒都有機會獲得鎖。ReentrantLock
預設情況下也是非公平的,但可以通過帶布林值的建構函式改用公平鎖。 - 鎖繫結多個條件:一個
ReentrantLock
物件可以通過多次呼叫newCondition()
同時繫結多個Condition
物件。而在synchronized
中,鎖物件的wait()
和notify()
或notifyAl()
只能實現一個隱含的條件,若要和多於一個的條件關聯不得不額外地新增一個鎖。
- 選擇:在
synchronized
能實現需求的情況下,優先考慮使用它來進行同步。下兩張圖是兩者在不同處理器上的吞吐量對比。
- 相同:用法與
- 使用
②非阻塞同步(Non-Blocking Synchronization):
- 基於衝突檢測的樂觀併發策略,即先進行操作,若無其他執行緒爭用共享資料,操作成功;反之產生了衝突再去採取其他的補償措施。
- 為了保證操作和衝突檢測這兩步具備原子性,需要用到硬體指令集,比如:
- 測試並設定(Test-and-Set)
- 獲取並增加(Fetch-and-Increment)
- 交換(Swap)
- 比較並交換(Compare-and-Swap,CAS)
- 載入連結/條件儲存(Load-Linked/Store-Conditional,LL/SC)
③無同步方案
- 定義:不用同步的方式保證執行緒安全,因為有些程式碼天生就是執行緒安全的。下面舉兩個例子:
- ①可重入程式碼(Reentrant Code)/純程式碼(Pure Code)
- 含義:可在程式碼執行的任何時刻中斷它去執行另外一段程式碼,當控制權返回後原來的程式並不會出現任何錯誤。
- 共同特徵:不依賴儲存在堆上的資料和公用的系統資源、用到的狀態量都由引數中傳入、不呼叫非可重入的方法…
- 判定依據:如果一個方法,它的返回結果是可預測的,只要輸入相同的資料就都能返回相同的結果,就滿足可重入性。
滿足可重入性的程式碼一定是執行緒安全的,反之,滿足執行緒安全的程式碼不一定是可重入的。
- ②執行緒本地儲存(Thread Local Storage)
- 含義:把共享資料的可見範圍限制在同一個執行緒之內,無須同步就能保證執行緒之間不出現資料爭用的問題。
- 使用
ThreadLocal
類可實現執行緒本地儲存的功能:每個執行緒的Thread
物件中都有一個ThreadLocalMap物件,它儲存了一組以ThreadLocal.threadLocalHashCode為key、以本地執行緒變數為value的鍵值對,而ThreadLocal物件就是當前執行緒的ThreadLocalMap的訪問入口,也就包含了一個獨一無二的threadLocalHashCode值,通過這個值就可以線上程鍵值值對中找回對應的本地執行緒變數。
3.鎖優化
解決併發的正確性之後,為了能線上程之間更『高效』地共享資料、解決競爭問題、提高程式的執行效率,下面介紹五種鎖優化技術。
a.適應性自旋(Adaptive Spinning)
- 背景:互斥同步在實現阻塞和喚醒時需要掛起執行緒和恢復執行緒的操作,都需要轉入核心態中完成,很影響系統的併發效能;同時,在許多應用上共享資料的鎖定狀態只是暫時,沒必要去掛起和恢復執行緒。
- 自旋鎖:當物理機器有多個處理器使得多個執行緒同時並行執行時,先讓後請求鎖的執行緒等待,但不放棄處理器的執行時間,看看持有鎖的執行緒是否很快就會釋放鎖,這時只需讓執行緒執行一個忙迴圈,即自旋。
- 注意:自旋等待不能代替阻塞,它雖然能避免執行緒切換的開銷,但會佔用處理器時間,因此自旋等待的時間必須要有一定的限度,如果自旋超過了限定的次數仍未成功獲鎖,就需要掛執行緒了。
- 自適應自旋鎖:自旋的時間不再固定,而是由該鎖上的上次自旋時間及鎖的擁有者的狀態共同決定。具體表現是:
- 如果對於某個鎖,自旋等待剛剛成功獲得,且持有鎖的執行緒正在執行中,那麼虛擬機器很可能允許自旋等待的時間更久點。
- 如果對於某個鎖,自旋很少成功獲得過,那麼很可能以後將省略自旋等待這個鎖,避免浪費處理器資源。
b.鎖消除(Lock Elimination)
- 鎖消除:指虛擬機器即時編譯器在執行時,對一些程式碼上要求同步,但是被檢測到不可能存在共享資料競爭的鎖進行消除。
- 判定依據:如果一段程式碼中堆上的所有資料都不會逃逸出去被其他執行緒訪問到,可把它們當做棧上資料對待,即執行緒私有的,無須同步加鎖。
c.鎖粗化(Lock Coarsening)
一般情況下,會將同步塊的作用範圍限制到只在共享資料的實際作用域中才進行同步,使得需要同步的運算元量儘可能變小,保證就算存在鎖競爭,等待鎖的執行緒也能儘快拿到鎖。
但如果反覆操作對同一個物件進行加鎖和解鎖,即使沒有執行緒競爭,頻繁地進行互斥同步操作也會導致不必要的效能損耗,此時,虛擬機器將會把加鎖同步的範圍粗化到整個操作序列的外部,這樣只需加一次鎖。
d.輕量級鎖(Lightweight Locking)
- 目的:在沒有多執行緒競爭的前提下,減少傳統的重量級鎖使用作業系統互斥量產生的效能消耗,注意不是用來代替重量級鎖的。
首先先理解HotSpot虛擬機器的物件頭的記憶體佈局:分為兩部分
- 第一部分用於儲存物件自身的執行時資料,這部分被稱為Mark Word,是實現輕量級鎖和偏向鎖的關鍵。如雜湊碼、GC分代年齡等。
- 另外一部分用於儲存指向方法區物件型別資料的指標,如果是陣列物件還會有一個額外的部分用於儲存陣列長度。
- 加鎖過程:程式碼進入同步塊時,如果同步物件未被鎖定(鎖標誌位為
01
),虛擬機器會在當前執行緒的棧幀中建立一個名為Lock Record的空間,用於儲存鎖物件Mark Word的拷貝。如下圖。
之後虛擬機器會嘗試用CAS操作將物件的Mark Word更新為指向Lock Record的指標。若更新動作成功,那麼當前執行緒就擁有了該物件的鎖,且物件Mark Word的鎖標誌位變為00
,即處於輕量級鎖定狀態;反之,虛擬機器會先檢查物件的Mark Word是否指向當前執行緒的棧幀,若當前執行緒已有該物件的鎖,可直接進入同步塊繼續執行,否則說明改物件已被其他執行緒搶佔。如下圖。
另外,如果有兩條以上的執行緒爭用同一個鎖,那輕量級鎖就不再有效,要膨脹為重量級鎖,鎖標誌位變為
10
,Mark Word中儲存的就是指向重量級鎖的指標,後面等待鎖的執行緒也要進入阻塞狀態。
- 解鎖過程:若物件的Mark Word仍指向著執行緒的Lock Record,就用CAS操作把物件當前的Mark Word和執行緒中複製的Displaced Mark Word替換回來。若替換成功,那麼就完成了整個同步過程;反之,說明有其他執行緒嘗試獲取該鎖,那麼就要在釋放鎖的同時喚醒被掛起的執行緒。
- 優點:因為對於絕大部分的鎖,在整個同步週期內都是不存在競爭的,所以輕量級鎖通過使用CAS操作消除同步使用的互斥量。
e.偏向鎖(Biased Locking)
- 目的:消除資料在無競爭情況下的同步原語,進一步提高程式的執行效能。
- 含義:偏向鎖會偏向於第一個獲得它的執行緒,如果在後面的執行中該鎖沒有被其他的執行緒獲取,則持有偏向鎖的執行緒將永遠不需要再進行同步。
- 加鎖過程:啟用偏向鎖的鎖物件在第一次被執行緒獲取時,Mark Word的鎖標誌位會被設定為
01
,即偏向模式,同時使用CAS操作把獲取到這個鎖的執行緒ID記錄在物件的Mark Word中。若操作成功,持有偏向鎖的執行緒以後每次進入這個鎖相關的同步塊時都可不再進行任何同步操作。 - 解鎖過程:當有另外的執行緒去嘗試獲取這個鎖時,根據鎖物件目前是否處於被鎖定的狀態,撤銷偏向後恢復到未鎖定
01
或輕量級鎖定00
的狀態,後續的同步操作就如輕量級鎖執行過程。如下圖。
- 優點:可提高帶有同步但無競爭的程式效能,但若程式中大多數鎖總被多個執行緒訪問,此模式就沒必要了。
相關推薦
要點提煉| 理解JVM之執行緒安全&鎖優化
本篇將介紹執行緒安全所涉及的概念和分類、同步實現的方式及虛擬機器的底層運作原理,以及虛擬機器為了實現高效併發所採取的一系列鎖優化措施。 概述 執行緒安全 鎖優化 1.概述 在要點提煉| 理解JVM之記憶體模型&執行緒中主要介紹了虛擬機
深入理解jvm 一 執行緒安全
執行緒安全:當多個執行緒訪問一個物件時,如果不用考慮這些執行緒在執行時環境下的排程和交替執行,也不需要進行額外的同步,或者在呼叫方進行任何其他的協調操作,呼叫這個物件的行為都可以活得正確的結果,那麼這個物件就是執行緒安全的。java語言中得各種操作共享資料可以分成五類1、不可
JVM學習篇(4)之執行緒安全與鎖優化
執行緒安全與鎖優化 Java中執行緒安全 對共享資料的操作 1. 不可變: 不可變的物件一定是執行緒安全的。如String類。 2. 絕對執行緒安全 3. 相對執行緒安全 4. 執行緒相容 5. 執行緒對
《深入理解Java虛擬機器》學習筆記之執行緒安全與鎖優化
二、執行緒安全 定義: “當多個執行緒訪問一個物件時,如果不用考慮這些執行緒在執行時環境下的排程和交替執行,也不需要進行額外的同步,或者在呼叫方進行任何其他的協調操作,呼叫這個物件的行為都可以獲得
十二、JVM(HotSpot)執行緒安全與鎖優化----終結篇
注:本博文主要是基於JDK1.7會適當加入1.8內容。 執行緒安全:當多個執行緒訪問一個物件時,如果不用考慮這些執行緒在執行環境下的排程和交替執行,也不需要進行額外的同步,或者在呼叫方進行任何其他的協調操作,呼叫這個物件的行為可以獲取正確的結果,那這個物件就是執行緒安全的。 1、Ja
Java併發程式設計之執行緒安全、執行緒通訊
Java多執行緒開發中最重要的一點就是執行緒安全的實現了。所謂Java執行緒安全,可以簡單理解為當多個執行緒訪問同一個共享資源時產生的資料不一致問題。為此,Java提供了一系列方法來解決執行緒安全問題。 synchronized synchronized用於同步多執行緒對共享資源的訪問,在實現中分為同步程
Java之執行緒安全問題
什麼是程序? 電腦中每個程式有一個獨立的程序,而程序之間是相互獨立存在的。 什麼是執行緒? 程序想要執行任務就需要依賴執行緒。程序中的最小執行單位就是執行緒,並且一個程序中至少有一個執行緒。
多執行緒之執行緒安全關鍵字synchronized
synchronized關鍵字,是多執行緒程式設計時保證執行緒安全使用非常廣泛的java知識。下面我們學習下synchronized的相關知識: 實現原理 synchronized的實現原理是基於記憶體中的lock原則。記憶體模型中的變
muduo_base程式碼剖析之執行緒安全的Sigleton模式
預備知識 什麼是單例模式? 一個類有且僅有一個例項,並且對外提供統一的訪問該例項的介面 單例模式分為餓漢式、懶漢式,對於餓漢式要保證執行緒安全需要使用double-check lock機制 muduo中實現的執行緒安全的Sigleton類,沒使用鎖,而使用效率更高
Java基礎多執行緒之執行緒安全-同步鎖三種形式
首先,我們通過一個案例,演示執行緒的安全問題: 電影院要賣票,我們模擬電影院的賣票過程。假設要播放的電影是 “葫蘆娃大戰奧特曼”,本次電影的座位共100個(本場電影只能賣100張票)。我們來模擬電影院的售票視窗,實現多個視窗同時賣 “終結者”這場電影票(多個視窗一起賣這100張票)需要視窗
單列模式之執行緒安全實現
單例模式有五種寫法:懶漢、餓漢、雙重檢驗鎖、靜態內部類、列舉 懶漢式執行緒不安全 public class Singleton { private static Singleton instance; private Singleton (){} publ
[JDK] Java 多執行緒 之 執行緒安全
Java 多執行緒 之 執行緒安全 多執行緒併發操作時資料共享如何安全進行? 執行緒安全與共享 多執行緒操作靜態變數(非執行緒安全) SynchronizedLockTest: /** * <p> * 測試類 * </p>
從零開始學多執行緒之執行緒安全(一)
public class Employees { 2 //程式設計師的等級 3 private int level; 4 //技能庫 5 public Map<String,String> skills; 6 7 //工資 8 pr
單例模式之執行緒安全解析
面試的時候,常常會被問到這樣一個問題:請您寫出一個單例模式(Singleton Pattern)吧。 單例的目的是為了保證執行時Singleton類只有唯一的一個例項,最常用的地方比如拿到資料庫的連線,Spring的中建立BeanFactory這些開銷比較大的操作,
Java多執行緒總結之執行緒安全佇列Queue
在Java多執行緒應用中,佇列的使用率很高,多數生產消費模型的首選資料結構就是佇列。Java提供的執行緒安全的Queue可以分為阻塞佇列和非阻塞佇列,其中阻塞佇列的典型例子是BlockingQueue,非阻塞佇列的典型例子是ConcurrentLinkedQueue,在實際
001_重拾多執行緒之執行緒安全問題總結
什麼是執行緒安全? 當多個執行緒訪問某個類時,這個類始終都能表現出正確的行為,那麼就稱這個類是執行緒安全的。(出自併發程式設計實戰) 當多個執行緒訪問某個類時,不管執行時環境採用何種排程方式或者這些執行緒將如何交替執行,並且在主調程式碼中不需要任何額外的同步或協同,這個類都能表現出正
Java多執行緒之 執行緒安全容器的非阻塞容器
在併發程式設計中,會經常遇到使用容器。但是如果一個容器不是執行緒安全的,那麼他在多執行緒的插入或者
多執行緒(四)執行緒的同步之執行緒安全問題
關於執行緒安全問題,有一個經典的問題:銀行取錢的問題。銀行取錢的基本流程可以分為如下幾個步驟: 1、使用者輸入賬號、密碼,系統判斷使用者的賬戶、密碼是否匹配; 2、使用者輸入取款金額; 3、系統判斷賬
【JVM】執行緒安全與鎖優化
執行緒安全 1.定義 當多個執行緒訪問一個物件時,如果不用考慮這些執行緒在執行時環境下的排程和交替行,也不需要進行額外的同步,或者在呼叫方進行任何其他的協調操作,呼叫這個物件的行為都可以獲得正確的結果 2.分類 (1)不可變 不可變的物件一定是執行緒安
java 單例模式之執行緒安全的餓漢模式和懶漢模式
單例模式 解決的問題:保證一個類在記憶體中的物件唯一性. 比如:多程式讀取一個配置檔案時,建議配置檔案封裝成物件。會方便操作其中資料,又要保證多個程式讀到的是同一個配置檔案物件, 就需要該配置檔案物件在記憶體中是唯一的。 如何保證物件唯一性呢? 思想: 1,不讓其他程式建立