synchronized關鍵字簡介 多線程中篇(十一)


前面說過,Java對象都有與之關聯的一個內部鎖和監視器
內部鎖是一種排它鎖,能夠保障原子性、可見性、有序性
從Java語言層面上說,內部鎖使用synchronized關鍵字實現
synchronized可以修飾方法,靜態方法和實例方法都可以,也可以修飾一段代碼({} 包裹)
image_5c6cade1_3d6c
synchronized修飾的方法被叫做同步方法
  • 修飾的靜態方法叫做同步靜態方法
  • 修飾的實例方法叫做同步實例方法
  • synchronized修飾的代碼塊(或者一整個方法)就是曾經說過的臨界區
synchronized關鍵字同步機制的使用,需要借助於鎖對象
synchronized關鍵字修飾靜態方法,鎖對象隱含的是該類的class實例對象;修飾的實例方法隱含的是該對象本身(this)
對於同步代碼段,則需要顯式的指定鎖對象

示例

image_5c6cade1_1bcd
注意:
對於鎖對象,應該聲明為final的
因為如果一旦鎖對象發生了變化,那么很可能使用的將不是同一個鎖對象,也就失去了同步的意義了,更甚一步,通常聲明為private final
如上代碼示例,借助於synchronized關鍵字,就可以實現原子性、可見性、有序性,所以對於該臨界區內的代碼,必然不會出現線程安全問題
但是這是一種排他鎖,也就是對臨界區的處理串行化,所以勢必影響性能

鎖泄漏

對於synchronized來說,這是一種內部鎖,對於鎖的申請和釋放,都是借助於底層實現的,換句話說你只需要使用synchronized關鍵字即可
底層JVM會幫助我們實現鎖的獲取與鎖的釋放,即使出現問題,也會釋放鎖,所以synchronized的內部鎖不存在鎖泄露問題
對於鎖泄漏,有時候可能是同一個線程持續操作,由於鎖的可重入性,所以並不會發現問題,但是對於高並發,這就很可能爆發出來問題了

調度

Java虛擬機會給每個內部鎖分配一個入口集 Entry Set,用於記錄等待獲得內部鎖的線程
多個線程競爭時,只會有一個線程獲得鎖,其他線程獲取失敗,會進入BLOCKED等待狀態,位於入口集的等待區中
鎖釋放后,會隨機的喚醒一個線程,Java虛擬機內部對於內部鎖是非公平的,也僅僅支持非公平調度,喚醒的線程可能會跟其他的線程競爭,所以他並不一定可以競選成功,可能會被再次置入等待狀態
這個過程跟前面介紹的監視器的過程是一樣的

鎖對象的確認

前面提到
synchronized修飾的同步實例方法,鎖對象為當前對象本身this;靜態方法鎖對象為該類型對應的xxx.class對象實例;
這都是隱式的,如何確認?其實很簡單
可以定義另外的方法顯式的聲明鎖對象為該對象this或者xxx.class對象實例,對其中一個線程進行sleep,觀察顯式方法對鎖的獲取情況,就可以佐證這一結論。
如果是不同的鎖的話,將不會收到任何影響,如果是同一個鎖就需要進行等待。

同步繼承性

synchronized關鍵字修飾的方法可以進行同步,對於同步方法的繼承性是什么樣子的?
比如父類中
public synchronized void service();
子類中
@override
public void service();
對於子類中的方法調用,並不會具有同步的特性,所以,一個方法是否具有同步的特性,在於這個方法本身是否有synchronized修飾

同步代碼塊

synchronized即可以修飾方法,也可以修飾代碼塊
為什么還要用同步代碼塊?直接加到方法上多省事兒?
synchronized同步保障了原子性、可見性、有序性,這個內部鎖機制是排他的,換言之,相當於部分串行
串行自然可以解決多線程安全問題,如果整個項目全部都是synchronized的方法,那么肯定不會有線程安全問題,但是為什么不這么做?還不是因為性能問題,多核CPU放在那里,難道就只是擺設嘛
既然是相當於串行,很顯然,串行化的代碼越多,那么效率必然將會越低,所以希望減少非必要的串行化,留給多核機器以及編譯器CPU更多的優化空間
所以同步代碼塊順勢而出
同步代碼塊保障了更少的“串行化”代碼,那么一個方法中,同步代碼塊之外的代碼是如何進行的?是異步的!
進入同步代碼塊之前會多線程並發,但是一旦執行到同步代碼塊,將會串行

小結

對於synchronized關鍵字,從應用層面上來說是非常簡單的,就只有代碼中的三種樣式,但是底層的原理是很復雜的,涉及到JMM以及原子性、可見性、有序性的概念
所以想要學習synchronized,務必要理解這些概念
對於多線程編程來說,synchronized更大程度上來說,更相當於是一個語法糖,底層的機制全部被封裝了,如果理解了底層的概念,語法糖的東西,就沒什么理解難度
原子性、可見性、有序性是問題根源,JMM是問題解決方案,編譯器、JVM底層負責實現,synchronized只是一個關鍵字而已,但是synchronized卻是完全代表了底層的一切
為什么說synchronized關鍵字修飾的方法(代碼段)是線程安全的?那是因為底層的原子性、可見性、有序性的保障。
Java中任何一個對象都有與之關聯的內部鎖和監視器,所以任何的一個對象都可以用來作為鎖對象
所以,借助於synchronized關鍵字和鎖對象,進行合理的安排,你一定可以編寫出來正確的並發程序(自身的安排組織不當怪不得synchronized)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM