日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

自顶向下彻底理解 Java 中的 Synchronized

發(fā)布時間:2025/4/16 java 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 自顶向下彻底理解 Java 中的 Synchronized 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

閱讀本文至少要知道 synchronized 用來是干什么的... 需要的前置知識還有 Java 對象頭和 Java 字節(jié)碼的部分知識。

synchronized 的使用

synchronized 有三種使用方式,三種方式鎖住的對象是不相同的。

鎖分為實例對象鎖class 對象鎖類對象鎖,注意這三種鎖是不一樣的。

  • 修飾實例方法,此時鎖住的是對象,鎖分為實例對象鎖
  • 修飾靜態(tài)方法,此時鎖住的是類對象鎖
  • 修飾代碼段,此時鎖住的是括號中的對象(synchronized(this)),可以是實例對象鎖或者 class 對象鎖(synchronized(Object.class))

此時出現(xiàn)了鎖住類和鎖住對象,要注意這兩個鎖是不同的,在一個線程拿到類的鎖時,另外一個線程是可以拿到對象的鎖的。

synchronized 底層語義實現(xiàn)

每個對象都存在著一個 monitor 與之關聯(lián),對象與其 monitor 之間的關系有存在多種實現(xiàn)方式,如 monitor 可以與對象一起創(chuàng)建銷毀或當線程試圖獲取對象鎖時自動生成,但當一個 monitor 被某個線程持有后,它便處于鎖定狀態(tài)。

當多個線程同時請求某個對象監(jiān)視器時,新請求鎖的線程將首先被加入到 ConetentionList 中。對象監(jiān)視器會設置幾種狀態(tài)用來區(qū)分請求的線程:

Contention List:所有請求鎖的線程將被首先放置到該競爭隊列

Entry List:Contention List 中那些有資格成為候選人的線程被移到Entry List

Wait Set:那些調用 wait 方法被阻塞的線程被放置到 Wait Set

OnDeck:任何時刻最多只能有一個線程正在競爭鎖,該線程稱為 OnDeck

Owner:獲得鎖的線程稱為 Owner

!Owner:釋放鎖的線程

代碼同步塊和方法級別的 synchronized 使用在JVM 層實現(xiàn)是不一樣的。

synchrionized 在代碼同步塊的入口插入 monitorenter,在同步塊出口插入monitorexit 來實現(xiàn)互斥,可以通過反編譯看到。

方法級別的同步是隱式的,在字節(jié)碼層面上沒有顯示出來。JVM 可以從方法常量池中的方法表結構中的 ACC_SYNCHRONIZED 字段訪問標志區(qū)分一個方法是否為同步方法。如果是同步方法,則去獲取 monitor,然后執(zhí)行方法。
具體可以查看《深入理解 JVM 虛擬機》 中字節(jié)碼一章。

對 synchronized 的優(yōu)化

JDK 1.6 實現(xiàn)了對鎖的大量優(yōu)化。可以分為兩種,一種是減少對 synchronized 的使用,一種是在特殊條件下使用更輕量級的鎖來代替 synchronized。

減少對鎖的使用

鎖消除

當編譯器檢測到一些被加上 synchronized 的代碼不存在競爭的時候(通過逃逸分析,感興趣可以去看一下《深入理解 Java 虛擬機》),就會被視為線程私有的,鎖會被安全的消除掉。

鎖粗化

當編譯器發(fā)現(xiàn) synchronized 被加入在循環(huán)當中,不斷的加鎖解鎖會有極大的效率問題。不要認為你不會寫出這么傻的代碼,JDK 中有許多方法是同步的,比如 HashTable 中的一些方法。

for (int i = 0; i < 100; i++) {synchronized (this) {//do something} }

編譯器會自動把它優(yōu)化成

synchronized (this) {for (int i = 0; i < 100; i++) {//do something} }

來減少鎖的獲取和釋放。

自旋鎖與自適應鎖

有相當多一段代碼在代碼同步塊中只運行一小會兒,如果為了等待這一會兒去掛起和恢復線程,切換線程帶來的開銷不是很值得,在引入了自旋鎖后,當遇到鎖被別的線程占用的時候,這個線程就進入一段忙循環(huán),這就是自旋。

但是如果多次忙循環(huán)后仍然獲取不到鎖,那么只能掛起線程將鎖升級為重量級鎖了。

自適應鎖會記錄之前在代碼同步快的運行時間來決定是否要執(zhí)行自旋以及自旋的時間,如果之前自旋成功過,那么這次也很有可能會自旋成功。如果之前自旋失敗,那么就省略掉自旋過程直接掛起線程避免浪費 CPU 資源。

通過輕量級鎖來代替 synchronized

輕量級鎖設計出來是想要在競爭較少的情況下減少 synchronized 的性能消耗,而不是用來代替 synchronized 的。想要看懂輕量級鎖的使用需要對 Java 對象頭有一定的了解。關于 Java 對象頭可以參考。好,接下來我就默認認為你懂 Mark Word 是什么了。

鎖的膨脹過程是 偏向鎖→輕量級鎖→重量級鎖,膨脹過程的單方向的。不能縮小回來。

下面是 Mark Word 的內容和鎖的關系。

存儲內容標志位狀態(tài)
對象哈希碼,對象分代年齡01未鎖定
指向記錄鎖指針00輕量級鎖定
指向重量級鎖指針10膨脹(重量級鎖定)
11GC 標記
偏向線程 id,時間戳,分代年齡01可偏向

偏向鎖

偏向鎖的思想就是:鎖經(jīng)常被同一個線程重復獲取,那么可以通過設置偏向鎖來避免使用重量級鎖。因為如果這段時間只有這一個線程在重復獲取這個對象的鎖,那么對這部分代碼的同步就是無意義的。

當線程獲取鎖的時候發(fā)現(xiàn) Mark Word 是未鎖定的狀態(tài),那么就采用 CAS 把這個 Mark Word 設置成偏向狀態(tài),把這個線程的 id 設置進去,然后如果這個線程再次獲取這個鎖的時候發(fā)現(xiàn)這個偏向鎖的 id 和當前線程的 id 一樣則不需要同步直接運行。

當有另外一個線程嘗試獲取這個偏向鎖的時候,鎖會恢復到未鎖定或者輕量級鎖的狀態(tài)。

  • 如果對象未被鎖定,則會變成未鎖定的,不可偏向的對象
  • 如果對象被鎖定了,則會變成輕量級鎖狀態(tài)

如果大多數(shù)鎖總是被多個不同的線程訪問,那么偏向模式就是多余的,可以 采用 --XX:UseBiaseLocking 來禁止偏向鎖來提高性能。

輕量級鎖

當線程進入一個代碼同步塊的時候,虛擬機將使用 CAS 將 Mark Word 更新為指向 Lock Record 的指針。如果成功則線程擁有這個對象鎖,mark word 將被設為 00。

如果更新失敗,則檢查該線程是否持有這個對象鎖,如果已經(jīng)持有則直接向下執(zhí)行

如果沒有持有這個對象鎖則輕量級鎖膨脹為重量級鎖,鎖標志狀態(tài)變?yōu)?10。

參考文獻

  • 周志明. 深入理解 Java 虛擬機 [M]. 機械工業(yè)出版社, 2011.
  • 方騰飛.Java 并發(fā)編程的藝術 [M]. 機械工業(yè)出版社, 2015.
  • 深入理解Java并發(fā)之synchronized實現(xiàn)原理
  • [JVM底層又是如何實現(xiàn)synchronized的

轉載于:https://www.cnblogs.com/zjmeow/p/9818739.html

總結

以上是生活随笔為你收集整理的自顶向下彻底理解 Java 中的 Synchronized的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。