您好,登錄后才能下訂單哦!
本篇內容介紹了“Java中的Synchronized原理是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
Synchronized是Java中的隱式鎖,它的獲取鎖和釋放鎖都是隱式的,完全交由JVM幫助我們操作,在了解Synchronized關鍵字之前,首先要學習的知識點就是Java的對象結構,因為Synchronized鎖就是存放在Java對象中的,Java對象結構如下圖所示:
可以清晰的看到Java對象由三部分組成,分別是對象頭、實例數據、填充數據,我們的鎖就存放在對象頭中,接下來我們將對對象結構做一個簡單的解析:
mark-down:對象標記字段占8個字節,用于存儲有關鎖的標記位等信息,從圖中可以看出有哈希值、輕量級鎖的標記位、偏向鎖標記位等。
Klass Pointer:Class對象的類型指針,它就是指向當前對象屬于哪個Class類的指針,jdk1.8默認開啟壓縮指針后占用4個字節,關閉壓縮指針后占用8個字節。
對象實際數據:這部分內容包括對象的所有成員變量,大小由各個成員變量決定,比如byte占用1個字節、int占用4個字節等。
對其填充:這部分內容僅僅只是做到空間補全,就是一個占位符的作用,因為HotSpot虛擬機的內存管理系統要求對象的起始地址必須是8字節的整數倍,因此如果出現對象實例沒有對齊的話,就需要通過對其填充來補充。
在mark-down鎖類型標記中,可以看到總共有五種類型,分別是無鎖、偏向鎖、輕量級鎖、重量級鎖、GC標記,所以如果只是使用2比特標記是無法完全被表示出來的,所以引入了一位偏向鎖標記,也就是說001為無鎖、101為偏向鎖。
上面介紹了對象結構,可以看到在Mark-down中會存儲不同的鎖信息,當鎖的狀態為重量級鎖(10)時,Mark-down中會存放一個指向Monitor對象的指針,這個Monitor對象也稱為監視器鎖。
synchronized的運行機制,就是JVM檢測到共享對象存在不同的競爭情況的時候,會自動切換到適合的鎖實現,這種切換就是鎖的升級、降級。(很多地方都說鎖只能升級,不能降級,其實這種說法是錯誤的,在《Java并發編程的藝術》書中說到,對于偏向鎖來說,它可以進行降級到無鎖狀態,也叫做偏向鎖的撤銷)。
那么現在就存在著三種不同的Monitor實現,分別是偏向鎖、輕量級鎖和重量級鎖。如果一個Monitor被一個線程持有的時候,就說明這個線程拿到了鎖。
Java中的Monitor是基于C++的ObjectMonitor實現的,它的主要成員包括:
_owner:指向持有ObjectMonitor對象的線程
_WaitSet:存放處于wait狀態的線程隊列,即調用wait()方法的線程
_EntryList:存放處于等待鎖Block狀態的線程隊列
_count:約為_WaitSet+_EntryList的節點數之和
_cxq:多個線程爭搶鎖,會先存入這個單向鏈表
_recursions:記錄重入次數
_object:存儲的Monitor對象
獲取Monitor對象的線程進入_owner區的時候,_count+1,如果線程調用了wait()方法,那么會釋放Monitor對象(釋放鎖),_owner恢復為空同時_count-1。此時該線程進入_WaitSet隊列中,等待被喚醒。
從上述的描述可以看出,synchronized關鍵字獲取鎖的關鍵在于每個對象的對象頭中,這也就能解釋了為什么synchronized()括號里存放任何對象都能獲得鎖的特征。
原子性,就是說一個操作要么完成,要么不完成,不存在完成一半的情況,也就是說這個操作是不可中斷的。
synchronized可以保證同一時間內只有一個線程拿到鎖,進入到代碼塊去執行代碼,這樣說如果不能理解,那么就想象下面的一個場景,有一個廁所只有一個坑位,并且廁所還上鎖了,就是為了防止多人一起上廁所的不文明現象,每個人上廁所都必須要去廁所管理員處繳費,繳費后才能拿到鎖再去上廁所,上完廁所再把要是還給廁所管理員,synchronized就是廁所管理員,保證一次只能有一個人拿到鎖,并且每個人用完廁所之后都必須要歸還鑰匙。
接下來看到下面一個同步加方法:
public static void add() { synchronized (Demo.class) { counter++; } }
將其進行反編譯后查看代碼:
javap -v -p Demo
public static void add(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC , ACC_SYNCHRONIZED Code: stack=2, locals=2, args_size=0 0: ldc #12 // class 2: dup 3: astore_0 4: monitorenter 5: getstatic #10 // Field counter:I 8: iconst_1 9: iadd 10: putstatic #10 // Field counter:I 13: aload_0 14: monitorexit 15: goto 23 18: astore_1 19: aload_0 20: monitorexit 21: aload_1 22: athrow 23: return Exception table:
可以看到有兩個指令明顯和monitor有關:
monitorenter:在判斷擁有同步標識 ACC_SYNCHRONIZED 搶先進入此方法的線程會優先擁有 Monitor 的 owner ,此時計數器 +1
monitorexit:當執行完退出后,計數器 -1,歸 0 后被其他進入的線程獲得
可見性指的是當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他的線程能夠立馬感知,能看到修改后的值。(線程的可見性和一個叫JMM的東西息息相關,后面會寫一篇關于可見性結合volatile關鍵字的文章)
而Synchronized擁有可見性,因為它加鎖和釋放鎖都有如下語義:
線程加鎖前,必須清空工作內存中共享變量的值,從而從主內存中讀取最新的共享變量的值。
線程釋放鎖時,必須把共享變量的值刷新到主內存中。
synchronized的可見性依賴于操作系統內核互斥鎖實現,相當于JVM中的lock,unlock,退出代碼塊時需要刷新共享變量到主內存中,這一點和volatile關鍵字不一樣,volatile關鍵字的可見性是依賴于內存屏障(也叫內存柵欄)來實現的。
as-if-serial,就是保證不管編譯器和處理器為了性能優化怎樣進行指令重排序,都需要保證單線程下的運行結果的正確性。也就是常說的:如果在本線程內觀察,所有的操作都是有序的,如果在一個線程觀察另一個線程,所有的操作都是無序的。
注意,這里的有序性和volatile是不一樣的,它并不是volatile的防止指令重排序。
可重入鎖的概念很簡單,就是一個線程可以多次獲取自己持有的對象鎖,這種鎖就是可重入鎖,同樣的釋放鎖也就需要釋放相同數量的鎖。synchronized鎖對象中就有一個計數器,用于存放獲取鎖的次數,也就是重入次數。
synchronized 鎖有四種交替升級的狀態:無鎖、偏向鎖、輕量級鎖和重量級,這幾個狀態隨著競爭情況逐漸升級,后續會補上一張完整的鎖升級圖。
“Java中的Synchronized原理是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。