Java必會的Synchronized底層原理剖析
synchronized作為Java程序員最常用同步工具,很多人卻對它的用法和實現(xiàn)原理一知半解,以至于還有不少人認(rèn)為synchronized是重量級鎖,性能較差,盡量少用。
但不可否認(rèn)的是synchronized依然是并發(fā)首選工具,連volatile、CAS、ReentrantLock都無法動搖synchronized的地位。synchronized是工作面試中的必備技能,今天就跟著一燈一塊深入剖析synchronized的底層原理。
1. synchronized作用
synchronized是Java提供一種隱式鎖,無需開發(fā)者手動加鎖釋放鎖。保證多線程并發(fā)情況下數(shù)據(jù)的安全性,實現(xiàn)了同一個時刻只有一個線程能訪問資源,其他線程只能阻塞等待,簡單說就是互斥同步。
2. synchronized用法
先看一下synchronized有哪幾種用法?
使用位置 | 被鎖對象 | 示例代碼 |
---|---|---|
實例方法 | 實例對象 | public synchronized void method() { …… } |
靜態(tài)方法 | class類 | public static synchronized void method() { …… } |
實例對象 | 實例對象 | public void method() { Object obj = new Object(); synchronized (obj) { …… } } |
類對象 | class類 | public void method() { synchronized (Demo.class) { …… } } |
this關(guān)鍵字 | 實例對象 | public void method() { synchronized (this) { …… } } |
可以看到被鎖對象只要有兩種,實例對象和class類。
- 由于靜態(tài)方法可以通過類名直接訪問,所以它跟直接加鎖在class類上是一樣的。
- 當(dāng)在實例方法、實例對象、this關(guān)鍵字上面加鎖的時候,鎖定范圍都是當(dāng)前實例對象。
- 實例對象上面的鎖和class類上面的鎖,兩者不互斥。
3. synchronized加鎖原理
當(dāng)我們使用synchronized在方法和對象上加鎖的時候,Java底層到底怎么實現(xiàn)加鎖的?
當(dāng)在類對象上加鎖的時候,也就是在class類加鎖,代碼如下:
/** * @author 一燈架構(gòu) * @apiNote Synchronized示例 **/ public class SynchronizedDemo { public void method() { synchronized (SynchronizedDemo.class) { System.out.println("Hello world!"); } } }
反編譯一下,看一下源碼實現(xiàn):
可以看到,底層是通過monitorenter和monitorexit兩個關(guān)鍵字實現(xiàn)的加鎖與釋放鎖,執(zhí)行同步代碼之前使用monitorenter加鎖,執(zhí)行完同步代碼使用monitorexit釋放鎖,拋出異常的時候也是用monitorexit釋放鎖。
寫成偽代碼,類似下面這樣:
/** * @author 一燈架構(gòu) * @apiNote Synchronized示例 **/ public class SynchronizedDemo { public void method() { try { monitorenter 加鎖; System.out.println("Hello world!"); monitorexit 釋放鎖; } catch (Exception e) { monitorexit 釋放鎖; } } }
當(dāng)在實例方法上加鎖,底層是怎么實現(xiàn)的呢?代碼如下:
/** * @author 一燈架構(gòu) * @apiNote Synchronized示例 **/ public class SynchronizedDemo { public static synchronized void method() { System.out.println("Hello world!"); } }
再反編譯看一下底層實現(xiàn):
這次只使用了一個ACC_SYNCHRONIZED關(guān)鍵字,實現(xiàn)了隱式的加鎖與釋放鎖。其實無論是ACC_SYNCHRONIZED關(guān)鍵字,還是monitorenter和monitorexit,底層都是通過獲取monitor鎖來實現(xiàn)的加鎖與釋放鎖。
而monitor鎖又是通過ObjectMonitor來實現(xiàn)的,虛擬機中ObjectMonitor數(shù)據(jù)結(jié)構(gòu)如下(C++實現(xiàn)的):
ObjectMonitor() { _header = NULL; _count = 0; // WaitSet 和 EntryList 的節(jié)點數(shù)之和 _waiters = 0, _recursions = 0; // 重入次數(shù) _object = NULL; _owner = NULL; // 持有鎖的線程 _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 ; }
圖上展示了ObjectMonitor的基本工作機制:
- 當(dāng)多個線程同時訪問一段同步代碼時,首先會進(jìn)入 _EntryList 隊列中等待。
- 當(dāng)某個線程獲取到對象的Monitor鎖后進(jìn)入臨界區(qū)域,并把Monitor中的 _owner 變量設(shè)置為當(dāng)前線程,同時Monitor中的計數(shù)器 _count 加1。即獲得對象鎖。
- 若持有Monitor的線程調(diào)用 wait() 方法,將釋放當(dāng)前持有的Monitor鎖,_owner變量恢復(fù)為null,_count減1,同時該線程進(jìn)入 _WaitSet 集合中等待被喚醒。
- 在_WaitSet 集合中的線程會被再次放到_EntryList 隊列中,重新競爭獲取鎖。
- 若當(dāng)前線程執(zhí)行完畢也將釋放Monitor并復(fù)位變量的值,以便其他線程進(jìn)入獲取鎖。
線程爭搶鎖的過程要比上面展示得更加復(fù)雜。除了_EntryList 這個雙向鏈表用來保存競爭的線程,ObjectMonitor中還有另外一個單向鏈表 _cxq,由兩個隊列來共同管理并發(fā)的線程。
以上就是Java必會的Synchronized底層原理剖析 的詳細(xì)內(nèi)容,更多關(guān)于Java Synchronized原理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IDEA maven compile報錯OutOfMemoryError(內(nèi)存溢出)解決及jvm分析
遇到Maven編譯時報OutOfMemoryError錯誤通常因為默認(rèn)的堆內(nèi)存大小不足,本文就來介紹一下OutOfMemoryError(內(nèi)存溢出)解決,具有一定的參考價值,感興趣的可以了解一下2024-10-10Java Spring JdbcTemplate基本使用詳解
JDBC已經(jīng)能夠滿足大部分用戶最基本的需求,但是在使用JDBC時,必須自己來管理數(shù)據(jù)庫資源如:獲取PreparedStatement,設(shè)置SQL語句參數(shù),關(guān)閉連接等步驟2021-10-10Spring中過濾器(Filter)和攔截器(Interceptor)的區(qū)別和聯(lián)系解析
在我們?nèi)粘5拈_發(fā)中,我們經(jīng)常會用到Filter和Interceptor,這篇文章主要介紹了Spring中過濾器(Filter)和攔截器(Interceptor)的區(qū)別和聯(lián)系?,需要的朋友可以參考下2022-10-10