成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java程序員進階必備:深入分析 Synchronized 原理

開發 后端
同步方法支持一種簡單的策略來防止線程干擾和內存一致性錯誤:如果一個對象對多個線程可見,則對該對象變量的所有讀取或寫入都是通過同步方法完成的。

深入分析 Synchronized 原理

我們在開發中肯定會遇到在同一個 JVM 中,存在多個線程同時操作同一個資源時,此時需要想要確保操作的結果滿足預期,就需要使用同步方法。

官方解釋:同步方法支持一種簡單的策略來防止線程干擾和內存一致性錯誤:如果一個對象對多個線程可見,則對該對象變量的所有讀取或寫入都是通過同步方法完成的。

官方推薦使用的同步方法 (JDK 1.6后):Synchronized 基于 JVM 實現(此次主角);當然還有 ReentrantLock 基于 JDK 實現的。

我們先簡單地熱個身,舉一個常用 synchronized 的方式(鎖的是該類的實例對象)。

public class SynchronizedCodeTest {
public void testSynchronized() throws InterruptedException {
synchronized (this) {
System.out.println("進入同步代碼塊");
Thread.sleep(100);
System.out.println("離開同步代碼塊");
}
}
public static void main(String[] args) throws InterruptedException {
new SynchronizedCodeTest().testSynchronized();
}
}

Synchronized 常用場景

任何對象(都有Mark Word結構,后面會詳細描述) 都可以能作為 synchronized 鎖的對象,根據使用的方式不同,鎖的對象和對應的粒度也是有所不同。

并發編程三大特性

簡單回顧了下 synchronized ,一聊到鎖就會提到 原子性、有序性、可見性,簡單的介紹下這些(就不具體展開說明了,有需要的讀者可以查閱相關資料,或者感興趣的話我后續補充)。

原子性

原子性:一個操作或者多個操作 要么全部執行并且執行的過程不會被任何因素打斷,要么就都不執行。

簡單理解為:如果將下單和支付2個操作看作一個整體,只要其中一個操作失敗了,都算失敗,反之成功。

有序性

有序性:即程序執行的順序按照代碼的先后順序執行。

大家可能或多或少,有聽說過 Java 為了提高性能允許重排序(編譯器重排序 和 處理器重排序),因此程序執行可能出現亂序也是由此而來。

簡單理解為:有序性保證了 同樣的代碼 在多線程和單線程執行的最后結果相同,按照代碼的先后順序執行。

可見性

可見性:當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。

某個類的一個成員變量 Integer A = 0;

# 線程1執行操作
A = 10
# 與此同時 線程2執行操作(B的值是0,而不是10,這就是可見性的問題)
Integer B = A;

# 常用的解決方案使用:volatile修飾 A 或者 使用synchronized修飾代碼塊 都可以解決這個問題

既然提到 synchronized 再多延伸出2個特性。

可重入性

synchronized monitor(鎖對象) 有個計數器,獲取鎖時 會記錄當前線程獲取鎖的次數,在執行完對應的代碼塊之后,計數器就會-1,直到計數器清零,鎖就會被釋放了。

不可中斷性

不可中斷:一個線程獲取鎖之后,另外一個線程處于阻塞或者等待狀態,前一個不釋放,后一個也一直會阻塞或者等待,不可以被中斷。

Synchronized是不可中斷,而 ReentrantLock是可中斷(二者比較重要的區別之一)。

Synchronized 字節碼

介紹完一些基本的特性后,我們正式開始進入 synchronized 實現原理分析。

# 將上面 熱身例子反編譯成字節碼
javac -verbose SynchronizedCodeTest.java
javap -c SynchronizedCodeTest

我們主要關注下,monitorenter 和 monitorexit 這2個指令,對應的是 當前線程獲取鎖&計數器加一 和 釋放鎖&計數器減一。多個線程獲取對象的監視器monitor獲取是互斥。

對象,對象監視器,同步隊列以及執行線程狀態之間的關系

任意線程對 Object 的訪問,首先要獲得 Object 的監視器,如果獲取失敗,該線程就進入同步狀態,線程狀態變為 BLOCKED,當 Object 的監視器占有者釋放后,在同步隊列中得線程就會有機會重新獲取該監視器。

Java 對象頭(Mark Word)

前面提到所有對象都可以作為synchronized鎖的對象,在同步的時候是獲取對象的monitor,即操作Java對象頭里的Mark Word 。

下面是32位為JVM Mark Word默認存儲結構(無鎖狀態)

  • 對象的 hashCode:25位的對象標識hashCode,采用延遲加載技術。調用方法System.identityHashCode()計算,并會將結果寫到該對象頭中。當對象加鎖后(偏向、輕量級、重量級),MarkWord的字節沒有足夠的空間保存hashCode,因此該值會移動到管程Monitor中。
  • **對象分代年齡 **:4位的Java對象年齡。每次 GC 未被回收累加的年齡就記錄在此處,默認達到15次進入老年代(-XX:MaxTenuringThreshold 可通過該配置進行修改進入老年代的閾值,最大值為15[age 只有 4bit])。
  • 是否是偏向鎖:1位的偏向鎖標志位。
  • 鎖標志位:2位鎖標志位,4種標志位后面展示說明。

鎖一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨著競爭情況逐漸升級。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。

接下來分別介紹這三種鎖的實現原理和步驟與上圖結合思考。

偏向鎖

HotSpot的作者經過研究發現,大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價更低而引入了偏向鎖。

偏向鎖的獲取

當一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word里是否存儲著指向當前線程的偏向鎖。如果測試成功,表示線程已經獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖):如果沒有設置,則使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。

偏向鎖的撤銷

偏向鎖使用了一種等到競爭出現才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖。

偏向鎖的獲得和撤銷流程

線程1--展示了偏向鎖獲取的過程。

線程2--展示了偏向鎖撤銷的過程。

輕量級鎖

輕量級鎖介于 偏向鎖與重量級鎖之間,競爭的線程不會阻塞。

輕量級加鎖

線程在執行同步塊之前,JVM會先在當前線程的棧楨中創建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復制到鎖記錄中,官方稱為 Displaced Mark Word。然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。

輕量級解鎖

輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到對象頭,如果成功,則表示沒有競爭發生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。下圖是兩個線程同時爭奪鎖,導致鎖膨脹的流程圖。

輕量級鎖及膨脹流程圖

因為自旋會消耗CPU,為了避免無用的自旋(比如獲得鎖的線程被阻塞住了),一旦鎖升級成重量級鎖,就不會再恢復到輕量級鎖狀態。

當鎖處于這個狀態下,其他線程試圖獲取鎖時,都會被阻塞住,當持有鎖的線程釋放鎖之后會喚醒這些線程,被喚醒的線程就會進行新一輪的奪鎖之爭。

重量級鎖

Synchronized 是通過對象內部的一個叫做 監視器鎖(Monitor)來實現的。但是監視器鎖本質又是依賴于底層的操作系統的Mutex Lock來實現的。而操作系統實現線程之間的切換這就需要從用戶態轉換到核心態,這個成本非常高,狀態之間的轉換需要相對比較長的時間,這就是為什么Synchronized效率低的原因。因此,這種依賴于操作系統 Mutex Lock 所實現的鎖我們稱之為 “重量級鎖”。

三種鎖的比較

分析了原理后,選擇哪種鎖就得看對應適用場景決定。

最后提一個 Synchronzied 避坑點(美團大佬分享):如果你的系統有很明確的 高低峰期,不建議使用 Synchronized,可以考慮使用 ReentrantLock。原因是 上面提到過 Synchronized 鎖的膨脹是不可逆的,導致一旦經歷了高峰期后就一直是重量級鎖,性能也會由此一直達到一個瓶頸上不去了。

責任編輯:姜華 來源: 今日頭條
相關推薦

2017-02-27 10:43:07

Javasynchronize

2022-04-12 08:30:45

TomcatWeb 應用Servlet

2022-10-28 10:23:27

Java多線程底層

2009-06-25 09:33:43

Java API程序員

2009-11-13 13:08:19

2010-09-07 14:21:22

PPPoE協議

2011-03-23 11:01:55

LAMP 架構

2015-08-03 09:54:26

Java線程Java

2020-12-07 06:23:48

Java內存

2019-09-25 11:39:07

程序員編程技術

2022-10-24 09:00:47

畫圖工具程序員XMind

2014-08-15 14:25:48

Android程序員資源

2014-08-20 10:28:29

Android

2011-06-11 20:59:12

程序員

2020-01-12 19:10:30

Java程序員數據

2020-05-09 11:20:02

Java結構圖虛擬機

2020-05-06 15:59:07

JavaScript程序員技術

2021-01-19 15:59:14

程序員算法

2023-02-01 08:13:30

Redis內存碎片

2011-09-01 13:51:52

JavaScript
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 男女视频在线免费观看 | 羞羞的视频网站 | 欧美日韩在线综合 | 日韩欧美国产一区二区三区 | 亚洲高清在线 | 欧美 日韩 中文 | 毛片一区| 九九视频在线观看视频6 | 免费看国产精品视频 | 国产在线中文字幕 | 国产成人网 | 91精品一区二区三区久久久久久 | 国产成人精品免费视频大全最热 | 成人在线a| 亚洲欧美日韩精品久久亚洲区 | 国产精品久久久久久久久久久免费看 | 国产激情免费视频 | 欧美自拍一区 | 亚洲国产精品91 | 午夜精品久久久久久久99黑人 | 精品日韩一区二区 | 免费黄色的网站 | 中文字幕亚洲一区二区三区 | 免费黄色在线 | 国产 亚洲 网红 主播 | 欧美一级二级视频 | 日韩电影中文字幕 | 国产一二区视频 | 日本三级线观看 视频 | av免费网站在线观看 | 日韩精品1区2区 | 亚洲一区二区三区在线免费 | 狠狠色综合网站久久久久久久 | 夜夜操av | 亚洲天堂男人的天堂 | 欧美三级三级三级爽爽爽 | 久久综合一区二区三区 | 99re视频这里只有精品 | 国产精品一区二区av | 天堂一区二区三区 | 在线成人av |