java并發(fā)之synchronized
前言:
Java為我們提供了隱式(synchronized聲明方式)和顯式(java.util.concurrentAPI編程方式)兩種工具來(lái)避免線(xiàn)程爭(zhēng)用。
本章節(jié)探索Java關(guān)鍵字synchronized。主要包含以下幾個(gè)內(nèi)容。
synchronized關(guān)鍵字的使用;synchronized背后的Monitor(管程);synchronized保證可見(jiàn)性和防重排序;- 使用
synchronized注意嵌套鎖定。
1、使用方式
synchronized 關(guān)鍵字有以下四種使用方式:
- 實(shí)例方法
- 靜態(tài)方法
- 實(shí)例方法中的代碼塊
- 靜態(tài)方法中的代碼塊
實(shí)例方法同步和實(shí)例方法代碼塊同步:
// 實(shí)例方法同步和實(shí)例方法代碼塊同步
public class SynchronizedTest {
private int count;
public void setCountPart(int num) {
synchronized (this) {
this.count += num;
}
}
public synchronized void setCount(int num) {
this.count += num;
}
}
靜態(tài)方法同步和靜態(tài)方法代碼塊同步:
// 靜態(tài)方法同步和靜態(tài)方法代碼塊同步
public class SynchronizedTest {
private static int count;
public static void setCountPart(int num) {
synchronized (SynchronizedTest.class) {
count += num;
}
}
public static synchronized void setCount(int num) {
count += num;
}
}
使用關(guān)鍵字synchronized實(shí)現(xiàn)同步是在JVM內(nèi)部實(shí)現(xiàn)處理,對(duì)于應(yīng)用開(kāi)發(fā)人員來(lái)說(shuō)它是隱式進(jìn)行的。
每個(gè)Java對(duì)象都有一個(gè)與之關(guān)聯(lián)的monitor。
當(dāng)線(xiàn)程調(diào)用實(shí)例同步方法時(shí),會(huì)自動(dòng)獲取實(shí)例對(duì)象的monitor。
當(dāng)線(xiàn)程調(diào)用靜態(tài)同步方法時(shí),會(huì)自動(dòng)獲取該類(lèi)Class實(shí)例對(duì)象的monitor。
Class實(shí)例:JVM為每個(gè)加載的class創(chuàng)建了對(duì)應(yīng)的Class實(shí)例來(lái)保存class及interface的所有信息;
2、Monitor(管程)
Monitor 直譯為監(jiān)視器,中文圈里稱(chēng)為管程。它的作用是讓線(xiàn)程互斥,保護(hù)共享數(shù)據(jù),另外也可以向其它線(xiàn)程發(fā)送滿(mǎn)足條件的信號(hào)。
如下圖,線(xiàn)程通過(guò)入口隊(duì)列(Entry Queue)到達(dá)訪(fǎng)問(wèn)共享數(shù)據(jù),若有線(xiàn)程占用轉(zhuǎn)移等待隊(duì)列(Wait Queue),線(xiàn)程訪(fǎng)問(wèn)共享數(shù)據(jù)完后觸發(fā)通知或轉(zhuǎn)移到信號(hào)隊(duì)列(Signal Queue)。

2.1 關(guān)于管程模型
網(wǎng)上查詢(xún)很多文章,大多數(shù)羅列 “ Hasen 模型、Hoare 模型和 MESA模型 ”這些名詞,看過(guò)之后我還是一知半解。本著對(duì)知識(shí)的求真,查找溯源,找到了以下資料。
為什么會(huì)有這三種模型?
假設(shè)有兩個(gè)線(xiàn)程A和B,線(xiàn)程B先進(jìn)入monitor執(zhí)行,線(xiàn)程A處于等待。當(dāng)線(xiàn)程A執(zhí)行完準(zhǔn)備退出的時(shí)候,是先退出monitor還是先喚醒線(xiàn)程A?這時(shí)就出現(xiàn)了Mesa語(yǔ)義, Hoare語(yǔ)義和Brinch Hansen語(yǔ)義 三種不同版本的處理方式。
2.2 Mesa Semantics
Mesa模型中 線(xiàn)程只會(huì)出現(xiàn)在WaitQueue,EntryQueue,Monitor。
當(dāng)線(xiàn)程B發(fā)出信號(hào)告知線(xiàn)程A時(shí),線(xiàn)程A從WaitQueue 轉(zhuǎn)移到EntryQueue并等待線(xiàn)程B退出Monitor之后再進(jìn)入Monitor。也就是先通知再退出。

2.3 Brinch Hanson Semantics
Brinch Hanson模型和Mesa模型類(lèi)似區(qū)別在于僅允許線(xiàn)程B退出Monitor后才能發(fā)送信號(hào)給線(xiàn)程A。也就是先退出再通知。

2.4 Hoare Semantics
Hoare模型中 線(xiàn)程會(huì)分別出現(xiàn)在WaitQueue,EntryQueue,SignalQueue,Monitor中。
當(dāng)線(xiàn)程B發(fā)出信號(hào)告知線(xiàn)程A并且退出Monitor轉(zhuǎn)移到SignalQueue,線(xiàn)程A進(jìn)入Monitor。當(dāng)線(xiàn)程A離開(kāi)Monitor后,線(xiàn)程B再次回到Monitor。

https://www.andrew.cmu.edu/course/15-440-kesden/applications/ln/lecture6.html
https://cseweb.ucsd.edu/classes/sp17/cse120-a/applications/ln/lecture8.html
Java里面monitor是如何處理?
我們通過(guò)反編譯class文件看下Synchronized工作原理。
public class SynchronizedTest {
private int count;
public void setCountPart(int num) {
synchronized (this) {
this.count += num;
}
}
}
編譯和反編譯命令
javac SynchronizedTest.java javap -v SynchronizedTest
我們看到兩個(gè)關(guān)鍵指令 monitorenter 和 monitorexit

2.5 monitorenter
Each object has a monitor associated with it. The thread that executes monitorenter gains ownership of the monitor associated with objectref. If another thread already owns the monitor associated with objectref, the current thread ……
每個(gè)對(duì)象都有一個(gè)關(guān)聯(lián)monitor。
線(xiàn)程執(zhí)行 monitorenter 時(shí)嘗試獲取關(guān)聯(lián)對(duì)象的monitor。
獲取時(shí)如果對(duì)象的monitor被另一個(gè)線(xiàn)程占有,則等待對(duì)方釋放monitor后再次嘗試獲取。
如果獲取成功則monitor計(jì)數(shù)器設(shè)置為1并將當(dāng)前線(xiàn)程設(shè)為monitor擁有者,如果線(xiàn)程再次進(jìn)入計(jì)數(shù)器自增,以表示進(jìn)入次數(shù)。
2.6 monitorexit
The current thread should be the owner of the monitor associated with the instance referenced by objectref……
線(xiàn)程執(zhí)行monitorexit 時(shí),monitor計(jì)數(shù)器自減,當(dāng)計(jì)數(shù)器變?yōu)?時(shí)釋放對(duì)象monitor。
原文:https://docs.oracle.com/javase/specs/jvms/se6/html/Instructions2.doc9.html
3、可見(jiàn)性和重排序
在介紹Java并發(fā)內(nèi)存模型詳情的時(shí)候,我們提到過(guò)線(xiàn)程訪(fǎng)問(wèn)共享對(duì)象時(shí)會(huì)先拷貝副本到CPU緩存,修改后返回CPU緩存,然后等待時(shí)機(jī)刷新到主存。這樣一來(lái)另外線(xiàn)程讀到的數(shù)據(jù)副本就不是最新,導(dǎo)致了數(shù)據(jù)的不一致,一般也將這種問(wèn)題稱(chēng)為線(xiàn)程可見(jiàn)性問(wèn)題。
不過(guò)在使用synchronized關(guān)鍵字的時(shí)候,情況有所不同。線(xiàn)程在進(jìn)入synchronized后會(huì)同步該線(xiàn)程可見(jiàn)的所有變量,退出synchronized后,會(huì)將所有修改的變量直接同步到主存,可視為跳過(guò)了CPU緩存,這樣一來(lái)就避免了可見(jiàn)性問(wèn)題。
另外Java編譯器和Java虛擬機(jī)為了達(dá)到優(yōu)化性能的目的會(huì)對(duì)代碼中的指令進(jìn)行重排序。但是重排序會(huì)導(dǎo)致多線(xiàn)程執(zhí)行出現(xiàn)意想不到的錯(cuò)誤。使用synchronized關(guān)鍵字可以消除對(duì)同步塊共享變量的重排序。
4、局限與性能
synchronized給我們提供了同步處理的便利,但是它在某些場(chǎng)景下也存在局限性,比如以下場(chǎng)景。
- 讀多寫(xiě)少場(chǎng)景。讀動(dòng)作其實(shí)是安全,我們應(yīng)該嚴(yán)格控制寫(xiě)操作。替代方案使用讀寫(xiě)鎖
readwritelock。如果只有一個(gè)線(xiàn)程進(jìn)行寫(xiě)操作,可使用volatile關(guān)鍵字替代。 - 允許多個(gè)線(xiàn)程同時(shí)進(jìn)入場(chǎng)景。
synchronized限制了每次只有一個(gè)線(xiàn)程可進(jìn)入。替代方案使用信號(hào)量semaphore。 - 需要保證搶占資源公平性。
synchronized并不保證線(xiàn)程進(jìn)入的公平性。替代方案公平鎖FairLock。
關(guān)于性能問(wèn)題。進(jìn)入和退出同步塊操作性能開(kāi)銷(xiāo)很小,但是過(guò)大范圍設(shè)置同步或者在頻繁的循環(huán)中使用同步可能會(huì)導(dǎo)致性能問(wèn)題。
可重入,在monitorenter指令解讀中,可以看出synchronized是可重入,重入一般發(fā)生在同步方法嵌套調(diào)用中。不過(guò)要防止嵌套monitor死鎖問(wèn)題。
比如下面代碼會(huì)直接造成死鎖:
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
synchronized (lock2) {
}
}
}
public void method2() {
synchronized (lock2) {
synchronized (lock1) {
}
}
}
現(xiàn)實(shí)情況中,開(kāi)發(fā)一般都不會(huì)出現(xiàn)以上代碼。但在使用 wait() notify() 很可能會(huì)出現(xiàn)阻塞鎖定。下面是一個(gè)模擬鎖的實(shí)現(xiàn)。
- 線(xiàn)程A調(diào)用
lock(),進(jìn)入鎖定代碼執(zhí)行。 - 線(xiàn)程B調(diào)用
lock(),得到monitorObj的monitor后等待線(xiàn)程B喚醒。 - 線(xiàn)程A執(zhí)行完鎖定代碼后,調(diào)用
unlock(),在嘗試獲取monitorObj的monitor時(shí),發(fā)現(xiàn)有線(xiàn)程占用,也一直掛起。 - 這樣線(xiàn)程A B 就互相干瞪眼!
public class Lock{
protected MonitorObj monitorObj = new MonitorObj();
protected boolean isLocked = false;
public void lock() throws InterruptedException{
synchronized(this){
while(isLocked){
synchronized(this.monitorObj){
this.monitorObj.wait();
}
}
isLocked = true;
}
}
public void unlock(){
synchronized(this){
this.isLocked = false;
synchronized(this.monitorObj){
this.monitorObj.notify();
}
}
}
}
總結(jié):
到此這篇關(guān)于java并發(fā)之synchronized的文章就介紹到這了,更多相關(guān)java并發(fā)synchronized內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java并發(fā)編程深入理解之Synchronized的使用及底層原理詳解 下
- Java并發(fā)編程深入理解之Synchronized的使用及底層原理詳解 上
- Java并發(fā)之synchronized實(shí)現(xiàn)原理深入理解
- java并發(fā)編程之深入理解Synchronized的使用
- 詳解Java并發(fā)編程之內(nèi)置鎖(synchronized)
- 淺析Java 并發(fā)編程中的synchronized
- 淺析java并發(fā)中的Synchronized關(guān)鍵詞
- Java并發(fā) synchronized鎖住的內(nèi)容解析
- 詳解java并發(fā)編程(2) --Synchronized與Volatile區(qū)別
- 詳解Java利用同步塊synchronized()保證并發(fā)安全
相關(guān)文章
Java 給PDF簽名時(shí)添加可信時(shí)間戳的方法
這篇文章主要介紹了Java 給PDF簽名時(shí)添加可信時(shí)間戳,關(guān)于jar導(dǎo)入的問(wèn)題,本文給大家?guī)?lái)兩種方法,一種是手動(dòng)導(dǎo)入另一種是maven配置導(dǎo)入,需要的朋友可以參考下2021-07-07
如何通過(guò)Maven倉(cāng)庫(kù)安裝Spire系列的Java產(chǎn)品
這篇文章主要介紹了如何通過(guò)Maven倉(cāng)庫(kù)安裝Spire系列的Java產(chǎn)品,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07
Java編程實(shí)現(xiàn)鄰接矩陣表示稠密圖代碼示例
這篇文章主要介紹了Java編程實(shí)現(xiàn)鄰接矩陣表示稠密圖代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11
Java基于LoadingCache實(shí)現(xiàn)本地緩存的示例代碼
本文主要介紹了Java基于LoadingCache實(shí)現(xiàn)本地緩存的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
js判斷是否是移動(dòng)設(shè)備登陸網(wǎng)頁(yè)的簡(jiǎn)單方法
這篇文章主要介紹了js判斷是否是移動(dòng)設(shè)備登陸網(wǎng)頁(yè)的簡(jiǎn)單方法,需要的朋友可以參考下2014-02-02
javaDSL簡(jiǎn)單實(shí)現(xiàn)示例分享
DSL領(lǐng)域定義語(yǔ)言,用來(lái)描述特定領(lǐng)域的特定表達(dá)。比如畫(huà)圖從起點(diǎn)到終點(diǎn);路由中的從A到B。這是關(guān)于畫(huà)圖的一個(gè)簡(jiǎn)單實(shí)現(xiàn)2014-03-03

