java中实现线程互斥的关键词_简单的互斥同步方式——synchronized关键字详解
2. synchronized的原理和實(shí)現(xiàn)細(xì)節(jié)
2.1 synchronized可以用在那些地方
靜態(tài)方法,鎖對(duì)象為當(dāng)前類的class對(duì)象,不用顯式指定
實(shí)例方法,鎖對(duì)象為當(dāng)前實(shí)例對(duì)象,不用顯式指定
同步代碼塊,鎖對(duì)象為括號(hào)中指定的對(duì)象,必須顯式指定
被synchronized修飾的方法或者代碼塊,同一時(shí)刻只能有一個(gè)線程能夠訪問它。
2.2 synchronized是如何實(shí)現(xiàn)線程互斥訪問的
雖然對(duì)于同步方法和同步代碼塊的底層細(xì)節(jié)略有不同,但都可以這么理解:對(duì)于被synchronized關(guān)鍵字修飾的代碼區(qū)域,java虛擬機(jī)會(huì)在開始的位置插入monitorenter指令,而在結(jié)束和異常處插入monitorexit指令,每一個(gè)monitorenter指令必定有一個(gè)monitorexit指令與其配對(duì)。線程在執(zhí)行到monitorenter指令時(shí),將會(huì)嘗試獲取鎖對(duì)象的monitor(監(jiān)視器),該線程將進(jìn)入同步方法或同步代碼塊中,同時(shí)monitor被鎖定,防止被多個(gè)線程同時(shí)獲取。獲取monitor失敗的線程將被阻塞在同步塊或同步方法的入口處,整個(gè)過程如下圖所示。
2.3 對(duì)象鎖的monitor信息存儲(chǔ)在哪
正如上面介紹的那樣,任何對(duì)象都可以作為synchronized代碼塊的鎖,而每個(gè)對(duì)象鎖都有個(gè)monitor與之關(guān)聯(lián),線程通過獲取該monitor的所有權(quán)來實(shí)現(xiàn)對(duì)被同步代碼的互斥訪問。這個(gè)monitor信息存儲(chǔ)對(duì)象的對(duì)象頭重。
2.4 monitor信息在對(duì)象頭中的實(shí)現(xiàn)細(xì)節(jié)
對(duì)象鎖的monitor信息是存儲(chǔ)在該對(duì)象的對(duì)象頭中。對(duì)象頭的基本信息主要包括
由于鎖信息是在Mark Word中,我們繼續(xù)看看Mark Word到底包括哪些信息
內(nèi)容比較多,不用強(qiáng)行記憶,只要知道大概有些東西就行了。
3. synchronized的內(nèi)存語義
3.1 synchronized的可見性分析
由JMM的happens-before規(guī)則可知,對(duì)同步鎖的釋放happens-before于對(duì)同步鎖的獲取,因此在A線程釋放鎖之前對(duì)同步區(qū)域內(nèi)共享變量作的修改,另一個(gè)線程B獲取鎖后將能夠馬上看到這種變化。它實(shí)現(xiàn)的原理和volatile類似
A線程釋放鎖時(shí),JMM會(huì)把該線程對(duì)應(yīng)的工作內(nèi)存中的共享變量刷新到主存中
B線程獲取鎖后,JMM會(huì)把該線程對(duì)應(yīng)的工作內(nèi)存中的共享變量置為無效,同步代碼塊中的共享變量必須從主存中獲取。
因此,如果共享變量只在同步代碼塊或者同步方法中被用到,那它是沒必要用volatile修飾的!
3.2 synchronized禁止指令重排嗎
那同步代碼塊會(huì)禁止指令重排嗎?答案是不會(huì)。但只要同步塊中的共享變量對(duì)其他線程不可見,那么我們就不必?fù)?dān)心它引起的副作用。下面就是一個(gè)典型的例子
靜態(tài)內(nèi)部類的懶漢式實(shí)現(xiàn)
public class Singleton {
private Singleton() {
}
static class SingletonHolder {
public static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
這是線程安全的實(shí)現(xiàn)方式,盡管INSTANCE變量沒有用volatile修飾,但我們不用擔(dān)心指令重排序帶來的線程不安全問題。因?yàn)?/p>
INSTANCE = new Singleton();
是在類初始化的過程中執(zhí)行的.JVM在Class文件被加載后,被線程使用前會(huì)執(zhí)行類的初始化。這個(gè)過程會(huì)通過一個(gè)初始化鎖進(jìn)行同步(對(duì)于每一個(gè)類或接口,都有唯一的初始化鎖),也就是說只有一個(gè)線程能獲取該鎖并執(zhí)行這段代碼,其他無法線程無法訪問到INSTANCE變量,故重排序?qū)ζ渌€程不可見,整個(gè)過程是線程安全的。
4.鎖的優(yōu)化
為了減少頻繁獲得和釋放鎖的性能開銷,JVM做了一系列優(yōu)化。鎖一共分為4種狀態(tài),級(jí)別從低到高依次為:無鎖狀態(tài),偏向鎖狀態(tài),輕量級(jí)鎖狀態(tài),重量級(jí)鎖狀態(tài)。鎖的狀態(tài)會(huì)隨著競(jìng)爭(zhēng)的加劇逐漸升級(jí),獲得和釋放鎖的性能開銷也逐漸加大,且鎖只能升級(jí)而不能降級(jí)。為什么需要這么設(shè)置,它們各自又有什么特點(diǎn),什么時(shí)候會(huì)出發(fā)鎖的升級(jí)?
偏向鎖: 當(dāng)線程第一次訪問同步塊時(shí),鎖為偏向鎖,該線程將一直持有偏向鎖,直到有其他線程競(jìng)爭(zhēng)該鎖,該線程才釋放鎖,偏向鎖也升級(jí)為輕量級(jí)鎖。持有偏向鎖的線程只在第一次獲取時(shí)會(huì)進(jìn)行CAS同步,接下來的訪問和釋放鎖將不需要進(jìn)行任何同步操作。
輕量級(jí)鎖:輕量級(jí)鎖在獲取鎖的時(shí)候會(huì)使用CAS操作將鎖對(duì)象頭的mark word替換到線程的棧幀中,釋放鎖則使用CAS替換回來。競(jìng)爭(zhēng)線程不會(huì)阻塞,而是使用自旋的方式等待,同時(shí)輕量級(jí)鎖將升級(jí)為重量級(jí)鎖。
重量級(jí)鎖:獲取和釋放鎖更消耗性能,且競(jìng)爭(zhēng)鎖失敗的線程將被阻塞。
下面是鎖升級(jí)時(shí)的狀態(tài)圖
5. 總結(jié)
synchronized可以起到獨(dú)占鎖的作用,使多線程互斥的訪問被其修飾的代碼。雖然這種串行化會(huì)極大影響執(zhí)行速度,但是能很好的保證線程安全,是開發(fā)中最經(jīng)常使用的多線程技術(shù)之一。synchronized和volatile一樣保證了多線程之間的可見性,又由于線程訪問時(shí)的獨(dú)占性對(duì)其他線程屏蔽了指令重排的細(xì)節(jié),故而可以說是比volatile更重量級(jí)也更可靠的同步工具。
總結(jié)
以上是生活随笔為你收集整理的java中实现线程互斥的关键词_简单的互斥同步方式——synchronized关键字详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: conf.exe是什么文件的进程 安全吗
- 下一篇: ts 模板库文件_在ts文件中使用模板引