Java并發(fā)編程深入理解之Synchronized的使用及底層原理詳解 上
一、線程安全問題
1、臨界資源
多線程編程中,有可能會出現(xiàn)多個線程同時訪問同一個共享、可變資源的情況,這個資源我們稱之其為臨界資源;這種資源可能是:對象、變量、文件等。
- 共享:資源可以由多個線程同時訪問
- 可變:資源可以在其生命周期內(nèi)被修改
2、線程安全問題
當(dāng)多個線程同時訪問一個對象時,如果不用考慮這些線程在運行時環(huán)境下的調(diào)度和交替執(zhí)行,也不需要進行額外的同步,或者在調(diào)用方進行任何其他的協(xié)調(diào)操作,調(diào)用這個對象的行為都可以獲得正確的結(jié)果,那就稱這個對象是線程安全的,否則就是非線程安全的。
3、如何解決線程安全問題
互斥同步(Mutual Exclusion & Synchronization)是一種最常見也是最主要的并發(fā)正確性保障手段。同步是指在多個線程并發(fā)訪問共享數(shù)據(jù)時,保證共享數(shù)據(jù)在同一個時刻只被一條(或者是一些,當(dāng)使用信號量的時候)線程使用。而互斥是實現(xiàn)同步的一種手段,臨界區(qū)(Critical Section)、互斥量(Mutex)和信號量(Semaphore)都是常見的互斥實現(xiàn)方式。
在Java里面,最基本的互斥同步手段就是synchronized關(guān)鍵字,另外還有從JDK1.5開始引入了JUC里面的Lock接口,其中用的比較多的就是ReentrantLock,后面也會進行介紹。
二、synchronized使用介紹
synchronized是JVM內(nèi)置的,是可重入的,其使用方法有三種:加在static修飾的靜態(tài)方法上,加在普通方法上,同步代碼塊三種方式。
- 加在靜態(tài)方法上(public synchronized static void test()),鎖的是當(dāng)前類的Class對象
- 加在實例方法上(public synchronized void test()),鎖的是當(dāng)前對象
- synchronized同步代碼塊(synchronized(object) {......}),鎖的是synchronized后面括號里面的對象
從上面可以看出synchronized鎖的其實都是對象。
三、synchronized實現(xiàn)原理
1、synchronized底層指令:monitorenter和monitorexit
synchronized是基于JVM內(nèi)置鎖實現(xiàn),通過內(nèi)部對象Object Monitor(監(jiān)視器鎖)實現(xiàn),基于進入與退出Monitor對象實現(xiàn)方法與代碼塊同步,監(jiān)視器鎖的實現(xiàn)依賴底層操作系統(tǒng)的Mutex lock(互斥鎖)實現(xiàn),它是一個重量級鎖性能較低。當(dāng)然,JVM內(nèi)置鎖在1.5之后版本做了重大的優(yōu)化,如鎖粗化(Lock Coarsening)、鎖消除(Lock Elimination)、輕量級鎖(Lightweight Locking)、偏向鎖(Biased Locking)、適應(yīng)性自旋(Adaptive Spinning)等技術(shù)來減少鎖操作的開銷,內(nèi)置鎖的并發(fā)性能已經(jīng)基本與Lock持平。
注意:只有synchronized鎖升級為重量級鎖時才會用到Object Monitor(監(jiān)視器鎖)。
synchronized關(guān)鍵字被編譯成字節(jié)碼后會被翻譯成monitorenter 和monitorexit 兩條指令分別在同步塊邏輯代碼的起始位置與結(jié)束位置。
public class TestSynchronized {
private Object obj = new Object();
public void testLock() {
synchronized (obj) {
System.out.println("獲取了鎖");
}
}
}
我們通過javap -c TestSynchronized.class將上面代碼的class文件進行反匯編,可以看到如下所示:我們看到了monitorenter 和monitorexit 兩條指令,但是monitorexit卻出現(xiàn)了兩次,原因如下:
- 第一個monitorexit指令是同步代碼塊正常釋放鎖的一個標(biāo)志;
- 如果同步代碼塊中出現(xiàn)Exception或者Error,則會調(diào)用第二個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 鑾峰彇浜嗛攣
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
2、Object Monitor(監(jiān)視器鎖)機制
上面提到了,只有synchronized鎖升級為重量級鎖時才會用到Object Monitor(監(jiān)視器鎖)。我們看一下Object Monitor的實現(xiàn)機制是什么?查看OpenJDK源碼可以看到Object Monitor由C++語言實現(xiàn),打開JDK源碼目錄 “jdk\hotspot\src\share\vm\runtime“可以看到objectMonitor.hpp,這個就是監(jiān)視器鎖的實現(xiàn),截取一段代碼如下:
ObjectMonitor() {
_header = NULL; //對象頭
_count = 0; //記錄加鎖次數(shù),鎖重入時用到
_waiters = 0, //當(dāng)前有多少處于wait狀態(tài)的thread
_recursions = 0; //記錄鎖的重入次數(shù)
_object = NULL;
_owner = 0; //指向持有ObjectMonitor對象的線程
_WaitSet = NULL; //處于wait狀態(tài)的線程,會被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;//處于等待加鎖block狀態(tài)的線程,會被加入到該列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
其中幾個比較重要的字段:
- _header 對象頭,前面說過synchronized鎖升級為重量級鎖之后才會用到objectMonitor,這時候?qū)ο箢^的Mark word會有一個指向重量級鎖Monitor的指針
- _count 線程獲取鎖的次數(shù),每加鎖一次該值加1。
- _waiters 當(dāng)前有多少處于wait狀態(tài)的thread
- _recursions 鎖的重入次數(shù)
- _owner 指向持有ObjectMonitor對象的線程地址。
- _WaitSet 存放調(diào)用wait方法,而進入等待狀態(tài)的線程的隊列。
- _EntryList 處于等待加鎖block狀態(tài)的線程,會被加入到該列表
ObjectMonitor的加鎖解鎖過程如下圖所示,ObjectMonitor中有兩個隊列,_WaitSet 和 _EntryList,用來保存ObjectWaiter對象列表(每個等待鎖的線程都會被封裝成ObjectWaiter對象);整個monitor運行的機制過程如下:
- _owner指向持有ObjectMonitor對象的線程,當(dāng)多個線程同時訪問一段同步代碼時,首先會進入 _EntryList 集合
- 當(dāng)線程獲取到對象的monitor 后進入 _Owner 區(qū)域并把monitor中的owner變量設(shè)置為當(dāng)前線程的同時,monitor中的計數(shù)器count加1,
- 若已經(jīng)獲取鎖的線程調(diào)用 wait() 方法,將釋放當(dāng)前持有的monitor,owner變量恢復(fù)為null,count自減1,同時該線程進入 WaitSet集合中等待被喚醒。
- 若當(dāng)前線程執(zhí)行完畢也將釋放monitor(鎖)并復(fù)位變量的值,以便其他線程進入獲取monitor(鎖)。

下節(jié)將會介紹一下synchronized的鎖優(yōu)化和鎖升級過程
到此這篇關(guān)于Java并發(fā)編程深入理解之Synchronized的使用及底層原理詳解 上的文章就介紹到這了,更多相關(guān)Java 并發(fā)編程 Synchronized內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談collection標(biāo)簽的oftype屬性能否為java.util.Map
這篇文章主要介紹了collection標(biāo)簽的oftype屬性能否為java.util.Map,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
Java Scala實現(xiàn)數(shù)據(jù)庫增刪查改操作詳解
這篇文章主要介紹了Java Scala實現(xiàn)數(shù)據(jù)庫增刪查改操作,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-04-04
java數(shù)據(jù)結(jié)構(gòu)與算法之桶排序?qū)崿F(xiàn)方法詳解
這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)與算法之桶排序?qū)崿F(xiàn)方法,結(jié)合具體實例形式詳細(xì)分析了桶排序的概念、原理、實現(xiàn)方法與相關(guān)操作技巧,需要的朋友可以參考下2017-05-05

