Java?synchronized同步關(guān)鍵字工作原理
一、簡介
synchronized是一個同步關(guān)鍵字,在某些多線程場景下,如果不進(jìn)行同步會導(dǎo)致共享數(shù)據(jù)不安全,synchronized關(guān)鍵字就可以用于代碼同步。
synchronized主要有3種使用形式:
- 修飾普通同步方法
鎖的對象是當(dāng)前實(shí)例對象;
- 修飾靜態(tài)同步方法
鎖的對象是當(dāng)前的類的Class字節(jié)碼對象;
- 修飾同步代碼塊
鎖的對象是synchronized后面括號里配置的對象,可以是某個對象,也可以是某個類的.class對象;
二、synchronized的特性
1)、原子性
原子性指的是在一次或多次操作中,要么所有的操作都執(zhí)行并且不會受其他因素干擾而中斷,要么所有的操作都不執(zhí)行。
2)、可見性
可見性是指一個線程對共享變量進(jìn)行了修改,另一個線程可以立即讀取得到修改后的最新值。
獲取鎖時,會清空當(dāng)前線程工作內(nèi)存中共享變量的副本值,重新從主內(nèi)存中獲取變量最新的值;
釋放鎖時,會將工作內(nèi)存的值重新刷新回主內(nèi)存;
3)、有序性
有序性是指程序中代碼的執(zhí)行順序,Java在編譯時和運(yùn)行時會對代碼進(jìn)行優(yōu)化,會導(dǎo)致程序最終的執(zhí)行順序不一定就是我們編寫代碼時的順序。
例如,instance = new Singleton()實(shí)例化對象的語句分為三步:
- 1、分配對象的內(nèi)存空間;
- 2、初始化對象;
- 3、設(shè)置實(shí)例對象指向剛分配的內(nèi)存地址;
上述第二步操作需要依賴第一步,但是第三步操作不需要依賴第二步,所以執(zhí)行順序可能為:1->2->3、1->3->2,當(dāng)執(zhí)行順序?yàn)?->3->2時,可能實(shí)例對象還沒正確初始化,我們直接拿到使用的時候可能會報錯。
synchronized的有序性是依靠內(nèi)存屏障實(shí)現(xiàn)的,在 monitorenter 指令和 Load 屏障之后,會加一個 Acquire屏障,這個屏障的作用是禁止同步代碼塊里面的讀操作和外面的讀寫操作之間發(fā)生指令重排,在 monitorexit 指令前加一個Release屏障,也是禁止同步代碼塊里面的寫操作和外面的讀寫操作之間發(fā)生重排序。如下:
int a = 0; synchronize (this){ //monitorenter // Load內(nèi)存屏障 // Acquire屏障,禁止代碼塊內(nèi)部的讀,和外面的讀寫發(fā)生指令重排 int b = a; a = 10; //注意:內(nèi)部還是會發(fā)生指令重排 // Release屏障,禁止寫,和外面的讀寫發(fā)生指令重排 } //monitorexit //Store內(nèi)存屏障
4)、可重入特性
可重入指的就是一個線程可以多次執(zhí)行synchronized,重復(fù)獲取同一把鎖。
舉個例子:
public class RenentrantDemo { // 鎖對象 private static Object obj = new Object(); public static void main(String[] args) { // 自定義Runnable對象 Runnable runnable = () -> { // 使用嵌套的同步代碼塊 synchronized (obj) { System.out.println(Thread.currentThread().getName() + "第一次獲取鎖資源..."); synchronized (obj) { System.out.println(Thread.currentThread().getName() + "第二次獲取鎖資源..."); synchronized (obj) { System.out.println(Thread.currentThread().getName() + "第三次獲取鎖資源..."); } } } }; new Thread(runnable, "t1").start(); } }
運(yùn)行結(jié)果:
t1第一次獲取鎖資源...
t1第二次獲取鎖資源...
t1第三次獲取鎖資源...
三、synchonized的使用及通過反匯編分析其原理
修飾代碼塊
public class SynchronizedDemo01 { // 鎖對象 private static Object obj = new Object(); public static void main(String[] args) { synchronized (obj) { System.out.println("execute main()..."); } } }
使用javap -p -v .\SynchronizedDemo01.class命令對字節(jié)碼進(jìn)行反匯編,查看字節(jié)碼指令:
monitorenter指令
官網(wǎng)對monitorenter指令的介紹,就是說每一個對象都會和一個監(jiān)視器對象monitor關(guān)聯(lián),監(jiān)視器被占用時會被鎖住,其他線程無法來獲取該monitor。 當(dāng)JVM執(zhí)行某個線程的某個方法內(nèi)部的monitorenter時,它會嘗試去獲取當(dāng)前對象對應(yīng)的monitor的所有權(quán)。大體過程如下:
1. 若monior的進(jìn)入數(shù)為0,線程可以進(jìn)入monitor,并將monitor的進(jìn)入數(shù)置為1,當(dāng)前線程成為monitor的owner(擁有這把鎖的線程);
2. 若線程已擁有monitor的所有權(quán),允許它重入monitor,則進(jìn)入monitor的進(jìn)入數(shù)加1(記錄線程擁有鎖的次數(shù));
3. 若其他線程已經(jīng)占有monitor的所有權(quán),那么當(dāng)前嘗試獲取monitor的所有權(quán)的線程會被阻塞,直到monitor的進(jìn)入數(shù)變?yōu)?,才能重新嘗試獲取monitor的所有權(quán);
monitorexit指令
官網(wǎng)對monitorexit指令的介紹,就是說能執(zhí)行monitorexit指令的線程一定是擁有當(dāng)前對象的monitor的所有權(quán)的線程;執(zhí)行monitorexit時會將monitor的進(jìn)入數(shù)減1,當(dāng)monitor的進(jìn)入數(shù)減為0時,當(dāng)前線程退出。
為什么字節(jié)碼中存在兩個monitorexit指令?
其實(shí)第二個monitorexit指令,是在程序發(fā)生異常時候用到的,也就說明了synchronized在發(fā)生異常時,會自動釋放鎖。
ObjectMonitor對象監(jiān)視器結(jié)構(gòu)如下:
ObjectMonitor() { _header = NULL; //鎖對象的原始對象頭 _count = 0; //搶占當(dāng)前鎖的線程數(shù)量 _waiters = 0, //調(diào)用wait方法后等待的線程數(shù)量 _recursions = 0; //記錄鎖重入次數(shù) _object = NULL; _owner = NULL; //指向持有ObjectMonitor的線程 _WaitSet = NULL; //處于wait狀態(tài)的線程隊(duì)列,等待被喚醒 _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; //等待鎖的線程隊(duì)列 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0; }
修飾普通方法
public class SynchronizedDemo02 { public static void main(String[] args) { } // 修飾普通方法 public synchronized void add() { System.out.println("add..."); } }
使用javap -p -v .\SynchronizedDemo02.class命令對字節(jié)碼進(jìn)行反匯編,查看字節(jié)碼指令:
如上圖,我們可以看到同步方法在反匯編后,不再是通過插入monitorentry和monitorexit指令實(shí)現(xiàn),而是會增加 ACC_SYNCHRONIZED 標(biāo)識隱式實(shí)現(xiàn)的,如果方法表結(jié)構(gòu)(method_info Structure)中的ACC_SYNCHRONIZED標(biāo)志被設(shè)置,那么線程在執(zhí)行方法前會先去獲取對象的monitor對象,如果獲取成功則執(zhí)行方法代碼,執(zhí)行完畢后釋放monitor對象,如果monitor對象已經(jīng)被其它線程獲取,那么當(dāng)前線程被阻塞。
修飾靜態(tài)方法
public class SynchronizedDemo03 { public static void main(String[] args) { add(); } // 修飾靜態(tài)方法 public synchronized static void add() { System.out.println("add..."); } }
使用javap -p -v .\SynchronizedDemo03.class命令對字節(jié)碼進(jìn)行反匯編,查看字節(jié)碼指令:
四、synchronized鎖對象存在哪里
之前對對象的內(nèi)存布局的介紹中,我們知道一個對象,包括對象頭、實(shí)例數(shù)據(jù)、對齊填充。而對象頭又包括mark word標(biāo)記字、類型指針、數(shù)組長度(只有數(shù)組對象才有)。在mark word標(biāo)記字中,有一塊區(qū)域主要存放關(guān)于鎖的信息。
存在鎖對象的對象頭的MarkWord標(biāo)記字中。如下圖:
五、synchronized與lock的區(qū)別
區(qū)別 | synchronized | lock |
1 | 關(guān)鍵字 | 接口 |
2 | 自動釋放鎖 | 必須手動調(diào)用unlock()方法釋放鎖 |
3 | 不能知道線程是否拿到鎖 | 可以知道線程是否拿到鎖 |
4 | 能鎖住方法和代碼塊 | 只能鎖住代碼塊 |
5 | 讀、寫操作都阻塞 | 可以使用讀鎖,提高多線程讀效率; |
6 | 非公平鎖 | 通過構(gòu)造方法可指定是公平鎖/非公平鎖 |
六、總結(jié)
1、synchronized修飾代碼塊的時候,通過在生成的字節(jié)碼指令中插入monitorenter和monitorexit指令來完成對對象監(jiān)視器鎖的獲取和釋放;
2、synchronized修飾普通方法和靜態(tài)方法的時候,通過在字節(jié)碼中的方法頭信息中添ACC_SYNCHRONIZED標(biāo)識,線程在執(zhí)行方法前會先去獲取對象的monitor對象,如果獲取成功則執(zhí)行方法代碼,執(zhí)行完畢后釋放monitor對象;
3、synchronized修飾代碼塊,鎖的對象就是代碼塊中的對象;修飾普通方法的時候,鎖的對象就是當(dāng)前對象this;修飾靜態(tài)方法的時候,鎖的對象就是當(dāng)前類的Class字節(jié)碼對象(類對象);
4、使用synchronized修飾實(shí)例對象時,如果一個線程正在訪問實(shí)例對象的一個synchronized方法時,其它線程不僅不能訪問該synchronized方法,該對象的其它synchronized方法也不能訪問,因?yàn)橐粋€對象只有一個監(jiān)視器鎖對象,但是其它線程可以訪問該對象的非synchronized方法。
5、線程A訪問實(shí)例對象的非static synchronized方法時,線程B也可以同時訪問實(shí)例對象的static synchronized方法,因?yàn)榍罢攉@取的是實(shí)例對象的監(jiān)視器鎖,而后者獲取的是類對象的監(jiān)視器鎖,兩者不存在互斥關(guān)系。
到此這篇關(guān)于Java synchronized同步關(guān)鍵字工作原理的文章就介紹到這了,更多相關(guān)Java synchronized內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java同步鎖Synchronized底層源碼和原理剖析(推薦)
- java同步鎖的正確使用方法(必看篇)
- 95%的Java程序員人都用不好Synchronized詳解
- Java synchronized偏向鎖的概念與使用
- Java?synchronized輕量級鎖實(shí)現(xiàn)過程淺析
- Java synchronized重量級鎖實(shí)現(xiàn)過程淺析
- Java @Transactional與synchronized使用的問題
- Java?synchronized與死鎖深入探究
- Java synchronized與CAS使用方式詳解
- 淺析Java關(guān)鍵詞synchronized的使用
- synchronized及JUC顯式locks?使用原理解析
- java鎖synchronized面試常問總結(jié)
- Java?HashTable與Collections.synchronizedMap源碼深入解析
- Java?Synchronized鎖的使用詳解
- AQS加鎖機(jī)制Synchronized相似點(diǎn)詳解
- Java必會的Synchronized底層原理剖析
- 一個例子帶你看懂Java中synchronized關(guān)鍵字到底怎么用
- 詳解Java?Synchronized的實(shí)現(xiàn)原理
- Synchronized?和?ReentrantLock?的實(shí)現(xiàn)原理及區(qū)別
- Java同步鎖synchronized用法的最全總結(jié)
相關(guān)文章
MyBatis?Plus如何實(shí)現(xiàn)獲取自動生成主鍵值
這篇文章主要介紹了MyBatis?Plus如何實(shí)現(xiàn)獲取自動生成主鍵值問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(22)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望可以幫到你2021-07-07java實(shí)現(xiàn)爬蟲爬網(wǎng)站圖片的實(shí)例代碼
這篇文章主要介紹了java實(shí)現(xiàn)爬蟲爬網(wǎng)站圖片的實(shí)例代碼,需要的朋友可以參考下2018-06-06解決IDEA service層跳轉(zhuǎn)實(shí)現(xiàn)類的快捷圖標(biāo)消失問題
這篇文章主要介紹了解決IDEA service層跳轉(zhuǎn)實(shí)現(xiàn)類的快捷圖標(biāo)消失問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02spring batch使用reader讀數(shù)據(jù)的內(nèi)存容量問題詳解
這篇文章主要介紹了spring batch使用reader讀數(shù)據(jù)的內(nèi)存容量問題詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07