【Java虛擬機】線程安全與鎖優化

前言

  站在計算機的角度去抽象、解決問題,是面向過程的編程思想;站在現實世界的角度去抽象、解決問題,是面向對象的編程思想。然而計算機世界與現實世界存在一些差異,必須讓程序在計算機中正確無誤的進行,然後實現高效,即保證併發的正確性和實現線程的安全性。

線程安全

一、定義

  1.線程安全
  當多個線程訪問一個對象時,不考慮這些線程在運行時環境下的調度和交替執行,不需進行額外同步,或者在調用方法進行任何其他的協調操作,調用這個對象的行爲都可以獲得正確的結果,則這個對象是線程安全的。
  2.特徵
  代碼本身封裝了所有必要的正確性保障手段(如互斥同步等),使調用者無需關心多線程的問題,也無需採取任何措施保證多線程的正確調用。

二、Java語言中的線程安全

  1.分類
  按照線程安全的“安全程度”由強至弱排序,將各種操作共享的數據分爲5類:不可變、絕對線程安全、相對線程安全、線程兼容和線程對立
  2.具體分析
  • 不可變:不可變(Immutable)的對象一定是線程安全的,final關鍵字可以體現。
  • 絕對線程安全:定義嚴格,不管運行時環境如何,調用者都不需要任何額外的同步措施。
  • 相對線程安全:保證對這個對象單獨的操作是線程安全的,調用時不需做額外的保障,如Vector、HashTable、Collections的synchronizedCollection()方法包裝的集合等。
  • 線程兼容:對象本身不是線程安全的,通過調用端使用同步手段保證併發環境下,對象線程安全,如ArrayList、HashMap等。
  • 線程對立:無論調用端是否採取了同步措施,都無法在多線程環境下併發使用代碼。如Thread類的suspend()、 resume()兩個方法。

線程安全實現方法

  線程安全似乎是一件由代碼如何編寫來決定的事情,但虛擬機提供的同步和鎖機制也有非常重要的作用,同時更偏重於鎖機制。

一、互斥同步

  這是一種常見的併發正確性保障手段。
  1.概念
  同步指在多個線程併發訪問共享數據時,同一個時刻只有一個線程使用共享數據;互斥是實現同步的一種手段,臨界區、互斥量和信號量是實現互斥的主要方式。互斥是因,同步是果;互斥是方法,同步是目的。
  2.synchronized關鍵字
  synchronized關鍵字是實現互斥同步的基本手段,經過編譯後,會在同步塊前後形成monitorenter和monitorexit兩個字節碼指令,reference類型的參數致命要鎖定和解鎖的對象。
  synchronized同步塊對同一條線程是重入的,同步塊在已進入的線程執行完之前,會阻塞後面其他線程進入。
  Java線程映射到操作系統的原生線程上,要阻塞或喚醒一個線程,都需要操作系統幫忙完成,需要從用戶態轉換到核心態中,這會耗費很多的處理器時間,狀態轉換消耗的時間可能比用戶代碼執行時間還要長,因此synchronized是Java語言中一個重量級(Heavyweight)的操作。
  3.ReentrantLock重入鎖
  ReentrantLock也具備線程重入特性,在代碼寫法上,表現爲API層面的互斥鎖,lock() 、unlock()配合try/finally語句塊完成互斥同步,保證線程安全。
  增加了高級功能,主要有3個:等待可中斷、可實現公平鎖,以及可以綁定多個條件。
  4.synchronized與ReentrantLock的對比
  JDK1.5的單核和多核處理器情況下,兩者的吞吐量對比圖,多線程環境下synchronized的吞吐量下降得非常嚴重,而ReentrantLock則能基本保持在同一個比較穩定的水平上。JDK1.6及以上兩者的性能基本持平,提倡在synchronized能實現需求時,優先考慮使用synchronized進行同步。

在這裏插入圖片描述
在這裏插入圖片描述
  互斥同步主要問題是進行線程阻塞和喚醒帶來的性能問題,這種同步也叫阻塞同步(Blocking Synchronized),屬於一種悲觀的併發策略。

二、非阻塞同步

  非阻塞同步,是在硬件指令集發展後,基於衝突檢測的樂觀併發策略。
  1.常用的硬件指令
  測試並設置(Test-and-Set)
  獲取並增加(Fetch-and-Increment)
  交換(Swap)
  比較並交換(Compare-and-Swap)
  加載鏈接/條件存儲(Load-Linked/Store-Conditional)
  其中前3條是大多數指令集中的處理器指令,後面的兩條是現代處理器新增的。CAS不能涵蓋互斥同步的所有使用場景,存在“ABA”問題,大部分情況下ABA問題不會影響程序併發的正確性,如果要解決ABA問題,改用傳統的互斥同步會比原子類更高效。
  2.使用CAS操作的方法
  在JDK1.5後,纔有使用CAS操作的方法。sun.misc.Unsafe類中的compareAndSwapInt()和compareAndSwapLong等幾個方法包裝提供。
  3.反射手段或Java API間接使用(AtomicInteger類)

三、無同步方案

  如果一個方法不涉及共享數據,就不需要同步措施保證正確性,一些代碼本身是線程安全的,主要有下面兩類:
  1.可重入代碼
  如果一個方法的返回結果可以預測,輸入相同的數據,返回相同的結果,它就滿足可重入性要求,即線程安全。
  2.線程本地存儲
  共享數據的可見範圍限制在同一個線程內,無須同步保證線程間的數據爭用後的安全。java.lang.ThreadLocal實現線程本地存儲功能。

鎖優化

  高效併發是從JDK1.5到JDK1.6的一個重要改進,實現了各種鎖優化技術,使得線程之間更高效共享數據,解決共享數據競爭問題,如適應性自旋(Adaptive Spinning)、鎖消除(Lock Elimination)、鎖粗化(Lock Coarsening)、輕量級鎖(Lightweight Locking)和偏向鎖(Biased Locking)等。

一、自旋鎖與自適應自旋

  1.自旋鎖
  有一個以上的處理器,兩個或以上的線程同時執行,後面請求鎖的線程不放棄處理器的執行時間,執行一個忙循環(自旋),以便持有鎖的線程很快釋放鎖而獲取到鎖。
  2.自適應的自旋鎖
  自旋時間由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。
  3.總結
  自旋鎖在JDK1.6中默認開啓,參數-XX:+UseSpinning開啓。在鎖被佔用的時間短的情況下,適合使用自旋等待,自旋次數默認值是10次,參數-XX:PreBlockSpin控制。
  JDK1.6引入自適應自旋,程序運行和性能監控信息的完善,可以幫助虛擬機對程序鎖的狀況預測更準確。

二、鎖消除

  鎖消除在虛擬機即時編譯器運行時,對一些代碼要求同步,但被檢測到不存在共享數據競爭的鎖進行消除。
  1.判斷依據
  逃逸分析下,判斷一段代碼中,堆上所有數據都不會逃逸出去,不能被其他線程訪問到,則認爲他們是線程私有,當做棧上數據對待,同步加鎖就無必要。
  2.例子
  String是不可變類,對字符串連接操作總是生成新的String對象進行。JDK1.5之前,StringBuffer對象的append()操作來處理;JDK1.5及以後,會使用StringBuilder對象的append()操作處理。我們發現,StringBuffer.append()中都有一個同步塊,鎖裏面的代碼不會逃逸出去,其他線程無法訪問到它。因此,在即時編譯後,代碼中會忽略所有同步而直接執行了。

三、鎖粗化

  虛擬機在探測到一串零碎的操作都對同一個對象加鎖,將會把加鎖同步的範圍擴展(粗化)到整個操作序列的外部。

四、輕量級鎖

  在沒有多線程競爭的前提下,使用輕量級鎖來減少傳統的重量級鎖使用操作系統互斥量產生的性能消耗。
  1.虛擬機對象的內存佈局
  分爲兩部分:一部分是存儲對象自身的運行時數據,如hashCode、GC分代年齡等,官方稱之爲“Mark Word”;另一部分是存儲指向方法區對象類型數據的指針;若是數組對象,還會有一個額外部分存儲數組長度。
  2.輕量級鎖執行過程
  (1)未鎖定:代碼進入同步塊時,若同步對象未被鎖定(鎖標誌位01),虛擬機首先在當前線程的棧幀中建立一個名爲鎖記錄(Lock Record)的空間,存儲鎖對象目前Mark Word的拷貝(Displaced Mark Word),如下圖13-3;
  (2)輕量級鎖:虛擬機使用CAS操作將對象的Mark Word更新爲指向Lock Record的指針,更新動作成功,線程則擁有了該對象鎖,對象Mark Word的鎖標誌位爲00,對象處於輕量級鎖定狀態,如圖13-4;
  (3)重量級鎖:若CAS更新操作失敗,虛擬機首先檢查對象的Mark Word是否指向當前線程的棧幀,是則當前線程擁有此對象鎖,繼續執行同步塊代碼;否則鎖對象被其他線程搶佔,兩條以上線程爭用同一個鎖,輕量級鎖將膨脹爲重量級鎖,鎖標誌狀態值爲10,Mark Word中存儲的是指向重量級鎖(互斥量)的指針,後面等待所的線程進入阻塞狀態。

在這裏插入圖片描述

在這裏插入圖片描述

五、偏向鎖

  JDK1.6引入的一項鎖優化,在無競爭的情況下把整個同步都消除掉,連CAS操作都不用做。
  1.特點
  偏向於第一個獲得偏向鎖的線程,執行過程中,如果其他線程沒有獲取該鎖,則當前持有偏向鎖的線程不會進行同步。
  2.過程
  默認啓用偏向鎖,-XX:+UseBiasedLocking,當鎖對象第一次被線程獲取時,虛擬機將把對象頭中標誌位設爲01,偏向模式。使用CAS操作把獲取到偏向鎖的線程ID記錄在對象的Mark Word中;
  CAS操作成功,持有偏向鎖的線程每次進入此鎖相關的同步塊時,不進行任何同步操作(如Locking、Unlocking及對Mark Word的Update等);
  其他線程嘗試獲取偏向鎖時,偏向模式結束。
  3.偏向鎖、輕量級鎖的狀態轉換
  根據鎖對象目前是否處於被鎖定狀態,撤銷偏向(Remove Bias)後恢復到未鎖定(標誌位01)或輕量級鎖定(標誌位00)的狀態,後續的同步操作如輕量級鎖執行過程。

在這裏插入圖片描述

小結

  線程安全的概念和分類、同步實現的方式及虛擬機底層運作原理,一系列鎖優化措施實現高效併發,這些知識是高級程序員必備知識之一。
感謝您的訪問!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章