前面說過,Java對象都有與之關聯的一個内部鎖和螢幕
内部鎖是一種排它鎖,能夠保障原子性、可見性、有序性
從Java語言層面上說,内部鎖使用synchronized關鍵字實作
synchronized可以修飾方法,靜态方法和執行個體方法都可以,也可以修飾一段代碼({} 包裹)

synchronized修飾的方法被叫做同步方法
- 修飾的靜态方法叫做同步靜态方法
- 修飾的執行個體方法叫做同步執行個體方法
- synchronized修飾的代碼塊(或者一整個方法)就是曾經說過的臨界區
synchronized關鍵字同步機制的使用,需要借助于鎖對象
synchronized關鍵字修飾靜态方法,鎖對象隐含的是該類的class執行個體對象;修飾的執行個體方法隐含的是該對象本身(this)
對于同步代碼段,則需要顯式的指定鎖對象
示例
注意:
對于鎖對象,應該聲明為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)
原文位址:synchronized關鍵字簡介 多線程中篇(十一)