您好,登錄后才能下訂單哦!
如何理解Java Synchronized的使用及底層原理,針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡(jiǎn)單易行的方法。
多線程編程中,有可能會(huì)出現(xiàn)多個(gè)線程同時(shí)訪問同一個(gè)共享、可變資源的情況,這個(gè)資源我們稱之其為臨界資源;這種資源可能是:對(duì)象、變量、文件等。
共享:資源可以由多個(gè)線程同時(shí)訪問
可變:資源可以在其生命周期內(nèi)被修改
當(dāng)多個(gè)線程同時(shí)訪問一個(gè)對(duì)象時(shí),如果不用考慮這些線程在運(yùn)行時(shí)環(huán)境下的調(diào)度和交替執(zhí)行,也不需要進(jìn)行額外的同步,或者在調(diào)用方進(jìn)行任何其他的協(xié)調(diào)操作,調(diào)用這個(gè)對(duì)象的行為都可以獲得正確的結(jié)果,那就稱這個(gè)對(duì)象是線程安全的,否則就是非線程安全的。
互斥同步(Mutual Exclusion & Synchronization)是一種最常見也是最主要的并發(fā)正確性保障手段。同步是指在多個(gè)線程并發(fā)訪問共享數(shù)據(jù)時(shí),保證共享數(shù)據(jù)在同一個(gè)時(shí)刻只被一條(或者是一些,當(dāng)使用信號(hào)量的時(shí)候)線程使用。而互斥是實(shí)現(xiàn)同步的一種手段,臨界區(qū)(Critical Section)、互斥量(Mutex)和信號(hào)量(Semaphore)都是常見的互斥實(shí)現(xiàn)方式。
在Java里面,最基本的互斥同步手段就是synchronized關(guān)鍵字,另外還有從JDK1.5開始引入了JUC里面的Lock接口,其中用的比較多的就是ReentrantLock,后面也會(huì)進(jìn)行介紹。
synchronized是JVM內(nèi)置的,是可重入的,其使用方法有三種:加在static修飾的靜態(tài)方法上,加在普通方法上,同步代碼塊三種方式。
加在靜態(tài)方法上(public synchronized static void test()),鎖的是當(dāng)前類的Class對(duì)象
加在實(shí)例方法上(public synchronized void test()),鎖的是當(dāng)前對(duì)象
synchronized同步代碼塊(synchronized(object) {......}),鎖的是synchronized后面括號(hào)里面的對(duì)象
從上面可以看出synchronized鎖的其實(shí)都是對(duì)象。
synchronized是基于JVM內(nèi)置鎖實(shí)現(xiàn),通過內(nèi)部對(duì)象Object Monitor(監(jiān)視器鎖)實(shí)現(xiàn),基于進(jìn)入與退出Monitor對(duì)象實(shí)現(xiàn)方法與代碼塊同步,監(jiān)視器鎖的實(shí)現(xiàn)依賴底層操作系統(tǒng)的Mutex lock(互斥鎖)實(shí)現(xiàn),它是一個(gè)重量級(jí)鎖性能較低。當(dāng)然,JVM內(nèi)置鎖在1.5之后版本做了重大的優(yōu)化,如鎖粗化(Lock Coarsening)、鎖消除(Lock Elimination)、輕量級(jí)鎖(Lightweight Locking)、偏向鎖(Biased Locking)、適應(yīng)性自旋(Adaptive Spinning)等技術(shù)來減少鎖操作的開銷,內(nèi)置鎖的并發(fā)性能已經(jīng)基本與Lock持平。
注意:只有synchronized鎖升級(jí)為重量級(jí)鎖時(shí)才會(huì)用到Object Monitor(監(jiān)視器鎖)。
synchronized關(guān)鍵字被編譯成字節(jié)碼后會(huì)被翻譯成monitorenter 和monitorexit 兩條指令分別在同步塊邏輯代碼的起始位置與結(jié)束位置。
public class TestSynchronized { private Object obj = new Object(); public void testLock() { synchronized (obj) { System.out.println("獲取了鎖"); } } }
我們通過javap -c TestSynchronized.class將上面代碼的class文件進(jìn)行反匯編,可以看到如下所示:我們看到了monitorenter 和monitorexit 兩條指令,但是monitorexit卻出現(xiàn)了兩次,原因如下:
第一個(gè)monitorexit指令是同步代碼塊正常釋放鎖的一個(gè)標(biāo)志;
如果同步代碼塊中出現(xiàn)Exception或者Error,則會(huì)調(diào)用第二個(gè)monitorexit指令來保證釋放鎖
public void testLock(); Code: 0: aload_0 1: getfield #3 // Field obj:Ljava/lang/Object; 4: dup 5: astore_1 6: monitorenter 7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 10: ldc #5 // String 鑾峰彇浜?jiǎn)閿? 12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 15: aload_1 16: monitorexit 17: goto 25 20: astore_2 21: aload_1 22: monitorexit 23: aload_2 24: athrow 25: return Exception table: from to target type 7 17 20 any 20 23 20 any
上面提到了,只有synchronized鎖升級(jí)為重量級(jí)鎖時(shí)才會(huì)用到Object Monitor(監(jiān)視器鎖)。我們看一下Object Monitor的實(shí)現(xiàn)機(jī)制是什么?查看OpenJDK源碼可以看到Object Monitor由C++語言實(shí)現(xiàn),打開JDK源碼目錄 “jdk\hotspot\src\share\vm\runtime“可以看到objectMonitor.hpp,這個(gè)就是監(jiān)視器鎖的實(shí)現(xiàn),截取一段代碼如下:
ObjectMonitor() { _header = NULL; //對(duì)象頭 _count = 0; //記錄加鎖次數(shù),鎖重入時(shí)用到 _waiters = 0, //當(dāng)前有多少處于wait狀態(tài)的thread _recursions = 0; //記錄鎖的重入次數(shù) _object = NULL; _owner = 0; //指向持有ObjectMonitor對(duì)象的線程 _WaitSet = NULL; //處于wait狀態(tài)的線程,會(huì)被加入到_WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ;//處于等待加鎖block狀態(tài)的線程,會(huì)被加入到該列表 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0; }
其中幾個(gè)比較重要的字段:
_header 對(duì)象頭,前面說過synchronized鎖升級(jí)為重量級(jí)鎖之后才會(huì)用到objectMonitor,這時(shí)候?qū)ο箢^的Mark word會(huì)有一個(gè)指向重量級(jí)鎖Monitor的指針
_count 線程獲取鎖的次數(shù),每加鎖一次該值加1。
_waiters 當(dāng)前有多少處于wait狀態(tài)的thread
_recursions 鎖的重入次數(shù)
_owner 指向持有ObjectMonitor對(duì)象的線程地址。
_WaitSet 存放調(diào)用wait方法,而進(jìn)入等待狀態(tài)的線程的隊(duì)列。
_EntryList 處于等待加鎖block狀態(tài)的線程,會(huì)被加入到該列表
ObjectMonitor的加鎖解鎖過程如下圖所示,ObjectMonitor中有兩個(gè)隊(duì)列,_WaitSet 和 _EntryList,用來保存ObjectWaiter對(duì)象列表(每個(gè)等待鎖的線程都會(huì)被封裝成ObjectWaiter對(duì)象);整個(gè)monitor運(yùn)行的機(jī)制過程如下:
_owner指向持有ObjectMonitor對(duì)象的線程,當(dāng)多個(gè)線程同時(shí)訪問一段同步代碼時(shí),首先會(huì)進(jìn)入 _EntryList 集合
當(dāng)線程獲取到對(duì)象的monitor 后進(jìn)入 _Owner 區(qū)域并把monitor中的owner變量設(shè)置為當(dāng)前線程的同時(shí),monitor中的計(jì)數(shù)器count加1,
若已經(jīng)獲取鎖的線程調(diào)用 wait() 方法,將釋放當(dāng)前持有的monitor,owner變量恢復(fù)為null,count自減1,同時(shí)該線程進(jìn)入 WaitSet集合中等待被喚醒。
若當(dāng)前線程執(zhí)行完畢也將釋放monitor(鎖)并復(fù)位變量的值,以便其他線程進(jìn)入獲取monitor(鎖)。
關(guān)于如何理解Java Synchronized的使用及底層原理問題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。