Java并發(fā)編程必備之Synchronized關(guān)鍵字深入解析
一、前言
在Java多線程編程中,線程安全是非常重要的一個概念。為了防止多個線程同時訪問共享資源時出現(xiàn)數(shù)據(jù)不一致或其他競態(tài)條件問題,Java提供了synchronized
關(guān)鍵字和監(jiān)視器鎖(Monitor Lock)機(jī)制。這個博客將深入探討synchronized
的原理、使用場景以及與監(jiān)視器鎖的關(guān)系。
二、Synchronized關(guān)鍵字
synchronized
是Java中的一個關(guān)鍵字,用于在多線程環(huán)境中控制對共享資源的訪問。當(dāng)一個線程執(zhí)行synchronized
修飾的方法或代碼塊時,其他線程將無法訪問相同的資源,直到當(dāng)前線程釋放資源鎖。
2.1 Synchronized的特性
1. 互斥
Synchronized
會起到互斥效果, 某個線程執(zhí)行到某個對象的 synchronized
中時, 其他線程如果也執(zhí)行到同一個對象 synchronized 就會阻塞等待。
• 進(jìn)入 synchronized 修飾的代碼塊, 相當(dāng)于 加鎖
• 退出 synchronized 修飾的代碼塊, 相當(dāng)于 解鎖
synchronized (locker){ for (int i = 0; i < 5000; i++) { count++; } }
synchronized用的鎖是存在Java對象頭里的。
可以粗略理解成, 每個對象在內(nèi)存中存儲的時候, 都存有一塊內(nèi)存表示當(dāng)前的 “鎖定” 狀態(tài)(類似于廁所
的 “有人/無人”)。
如果當(dāng)前是 “無人” 狀態(tài), 那么就可以使用, 使用時需要設(shè)為 “有人” 狀態(tài)。
如果當(dāng)前是 “有人” 狀態(tài), 那么其他人無法使用, 只能排隊。
理解 “阻塞等待”。
針對每?把鎖, 操作系統(tǒng)內(nèi)部都維護(hù)了?個等待隊列. 當(dāng)這個鎖被某個線程占有的時候, 其他線程嘗試
進(jìn)行加鎖, 就加不上了, 就會阻塞等待, ?直等到之前的線程解鎖之后, 由操作系統(tǒng)喚醒?個新的線程,
再來獲取到這個鎖.
注意:
• 上一個線程解鎖之后, 下一個線程并不是立即就能獲取到鎖。而是要靠操作系統(tǒng)來 “喚醒”. 這也就
是操作系統(tǒng)線程調(diào)度的一部分工作。
• 假設(shè)有 A B C 三個線程, 線程 A 先獲取到鎖, 然后 B 嘗試獲取鎖, 然后 C 再嘗試獲取鎖, 此時 B 和 C
都在阻塞隊列中排隊等待.。但是當(dāng) A 釋放鎖之后, 雖然 B 比 C 先來的, 但是 B 不一定就能獲取到鎖,
而是和 C 重新競爭, 并不遵守先來后到的規(guī)則。
synchronized的底層是使用操作系統(tǒng)的mutex lock實現(xiàn)的。
2. 可重入
synchronized 同步塊對同?條線程來說是可重入的,不會出現(xiàn)自己把自己鎖死的問題。
理解 “把自己鎖死”
?個線程沒有釋放鎖, 然后又嘗試再次加鎖。
第?次加鎖, 加鎖成功
lock();
第?次加鎖, 鎖已經(jīng)被占用, 阻塞等待
lock();
按照之前對于鎖的設(shè)定, 第二次加鎖的時候, 就會阻塞等待. 直到第一次的鎖被釋放, 才能獲取到第二
個鎖.。但是釋放第?個鎖也是由該線程來完成, 結(jié)果這個線程已經(jīng)躺平了, 啥都不想干了, 也就無法進(jìn)行解鎖操作. 這時候就會死鎖。
Java 中的 synchronized
是 可重?鎖, 因此沒有上面的問題。
for (int i = 0; i < 50000; i++) { synchronized (locker) { synchronized (locker) { count++; } } }
在可重入鎖的內(nèi)部, 包含了 “線程持有者” 和 “計數(shù)器” 兩個信息。
• 如果某個線程加鎖的時候, 發(fā)現(xiàn)鎖已經(jīng)被人占用, 但是恰好占用的正是自己, 那么仍然可以繼續(xù)獲取到鎖, 并讓計數(shù)器自增。
• 解鎖的時候計數(shù)器遞減為 0 的時候, 才真正釋放鎖. (才能被別的線程獲取到)
三、synchronized 的使用示例
synchronized 本質(zhì)上要修改指定對象的 “對象頭”。從使用角度來看,synchronized 也勢必要搭配?個具體的對象來使用。
3.1 修飾代碼塊: 明確指定鎖哪個對象
1.鎖任意對象
public class Synchrinized1 { private static int count; public static void main(String[] args) { Object locker=new Object(); Thread t1=new Thread(()->{ for (int i = 0; i <10000 ; i++) { synchronized (locker){ count++;//鎖任意對象 } } }); } }
2.鎖當(dāng)前對象
public class Synchrinized1 { private static int count; public static void main(String[] args) { Object locker=new Object(); Thread t1=new Thread(()->{ for (int i = 0; i <10000 ; i++) { synchronized (this){ count++;//鎖當(dāng)前對象 } } }); } }
3.2直接修飾普通方法
public class Synchronized2{ public int count=0; synchronized public void methond(){ count++; } }
3.3 修飾靜態(tài)方法
public class Synchronized{ public static int count=0; public static synchronized void methon(){ count++; } }
四 、監(jiān)視器鎖(Monitor Lock)
Monitor Lock
(監(jiān)視器鎖)是一種高級同步機(jī)制,底層實現(xiàn)了synchronized
關(guān)鍵字的功能。每個對象都關(guān)聯(lián)一個監(jiān)視器鎖,當(dāng)線程進(jìn)入synchronized
塊時,線程會獲取該對象的監(jiān)視器鎖。
4.1 監(jiān)視器鎖的特點
- 互斥性:同一時間只有一個線程可以持有監(jiān)視器鎖。
- 可重入性:同一線程可以多次獲取已持有的監(jiān)視器鎖。
- 鎖升級:從
未鎖定狀態(tài)
到偏向鎖
,再到輕量級鎖
,最終升級為重量級鎖
。
4.2 Synchronized的底層實現(xiàn)
synchronized
關(guān)鍵字的實現(xiàn)依賴于JVM,而JVM底層使用了監(jiān)視器鎖(Monitor Lock)來實現(xiàn)線程的同步。
當(dāng)線程執(zhí)行synchronized
代碼時,JVM會檢查是否已經(jīng)獲得了監(jiān)視器鎖:
- 如果未加鎖,當(dāng)前線程會嘗試獲取鎖。
- 如果已加鎖,當(dāng)前線程將被阻塞,直到鎖被釋放。
4.3 onitor Lock的狀態(tài)
一個監(jiān)視器鎖有以下四種狀態(tài):
- 未鎖定狀態(tài):任何線程都可以競爭鎖。
- 偏向鎖狀態(tài):只允許一個線程持有鎖,減少競爭。
- 輕量級鎖狀態(tài):多個線程競爭鎖,但未發(fā)生旋轉(zhuǎn)或阻塞。
- 重量級鎖狀態(tài):線程競爭激烈,鎖被線程阻塞等待。
4.4 Synchronized與ReentrantLock的比較
4.5 常見問題及解決方案
1. 避免過度同步
過度同步會降低并發(fā)性能,應(yīng)盡量縮小同步范圍。
2. 避免死鎖
死鎖是由于多個線程互相持有對方資源而導(dǎo)致的??梢酝ㄟ^如下方式避免:
- 按順序加鎖:對多個鎖的加鎖順序進(jìn)行統(tǒng)一。
- 避免嵌套鎖:減少嵌套使用鎖的場景。
3. 數(shù)據(jù)一致性問題
在單例模式或共享變量的場景中,必須確保所有修改共享資源的操作都在同步塊內(nèi)。
五、總結(jié)
通過本文,我們深入探索了Java中的Synchronized
關(guān)鍵字,包括其互斥性和可重入性的特性。文章詳細(xì)介紹了Synchronized
的三種使用方式:修飾代碼塊、修飾普通方法和修飾靜態(tài)方法。同時,我們還解析了監(jiān)視器鎖(Monitor Lock)的底層實現(xiàn),以及Synchronized
與ReentrantLock
的對比。最后,文章總結(jié)了并發(fā)編程中常見問題的解決方案,如避免過度同步、防止死鎖和保持?jǐn)?shù)據(jù)一致性。本文旨在幫助開發(fā)者更好地理解和使用Synchronized
,從而編寫出高效、安全的并發(fā)程序。
到此這篇關(guān)于Java并發(fā)編程必備之Synchronized關(guān)鍵字深入解析的文章就介紹到這了,更多相關(guān)Java Synchronized關(guān)鍵字內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 深入了解Java中Synchronized關(guān)鍵字的實現(xiàn)原理
- 一個例子帶你看懂Java中synchronized關(guān)鍵字到底怎么用
- Java關(guān)鍵字synchronized原理與鎖的狀態(tài)詳解
- Java中的synchronized關(guān)鍵字
- java中synchronized關(guān)鍵字的3種寫法實例
- Java中關(guān)鍵字synchronized的使用方法詳解
- Java synchronized關(guān)鍵字和Lock接口實現(xiàn)原理
- JAVA面試題 簡談你對synchronized關(guān)鍵字的理解
- Java中synchronized關(guān)鍵字引出的多種鎖 問題
- Java中synchronized關(guān)鍵字修飾方法同步的用法詳解
相關(guān)文章
SpringBoot啟動時自動執(zhí)行指定方法的幾種實現(xiàn)方式
在Spring Boot應(yīng)用程序中,要實現(xiàn)在應(yīng)用啟動時自動執(zhí)行某些代碼,本文主要介紹了SpringBoot啟動時自動執(zhí)行指定方法的幾種方式,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下2024-03-03SpringBoot日志進(jìn)階實戰(zhàn)之Logback配置經(jīng)驗和方法
本文給大家介紹在SpringBoot中使用Logback配置日志的經(jīng)驗和方法,并提供了詳細(xì)的代碼示例和解釋,包括:滾動文件、異步日志記錄、動態(tài)指定屬性、日志級別、配置文件等常用功能,覆蓋日常Logback配置開發(fā)90%的知識點,感興趣的朋友跟隨小編一起看看吧2023-06-06SpringBoot學(xué)習(xí)之Json數(shù)據(jù)交互的方法
這篇文章主要介紹了SpringBoot學(xué)習(xí)之Json數(shù)據(jù)交互的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12