Java同步鎖synchronized用法的最全總結(jié)
一、并發(fā)同步問題
線程安全是Java并發(fā)編程中的重點(diǎn),而造成線程安全問題的主要原因有兩點(diǎn),一是存在共享數(shù)據(jù)(也稱臨界資源),二是存在多條線程共同操作共享數(shù)據(jù)。因此,當(dāng)存在多個(gè)線程操作共享數(shù)據(jù)時(shí),需要保證同一時(shí)刻有且只有一個(gè)線程在操作共享數(shù)據(jù),其他線程必須等到該線程處理完數(shù)據(jù)后再進(jìn)行,這種方式就叫互斥鎖。也就是說當(dāng)一個(gè)共享數(shù)據(jù)被正在訪問的線程加上互斥鎖后,在同一個(gè)時(shí)刻,其他線程只能處于等待的狀態(tài),直到當(dāng)前線程處理完畢釋放該鎖。在 Java 中,關(guān)鍵字 synchronized可以保證在同一個(gè)時(shí)刻,只有一個(gè)線程可以執(zhí)行某個(gè)方法或者某個(gè)代碼塊(主要是對(duì)方法或者代碼塊中存在共享數(shù)據(jù)的操作),同時(shí)synchronized還有另外一個(gè)重要的作用,它可以可保證一個(gè)線程的變化(主要是共享數(shù)據(jù)的變化)被其他線程所看到(保證可見性,完全可以替代Volatile功能)。
二、鎖的簡(jiǎn)介
synchronized是Java的關(guān)鍵字,是一種同步鎖。
Java的內(nèi)置鎖:每個(gè)java對(duì)象都可以用做一個(gè)實(shí)現(xiàn)同步的鎖,這些鎖稱為內(nèi)置鎖。線程進(jìn)入同步代碼塊或方法的時(shí)候會(huì)自動(dòng)獲得該鎖,在退出同步代碼塊或方法時(shí)會(huì)釋放該鎖。獲得內(nèi)置鎖的唯一途徑就是進(jìn)入這個(gè)鎖的保護(hù)的同步代碼塊或方法。
Java內(nèi)置鎖是一個(gè)互斥鎖,這就是意味著最多只有一個(gè)線程能夠獲得該鎖,當(dāng)線程A嘗試去獲得線程B持有的內(nèi)置鎖時(shí),線程A必須等待或者阻塞,直到線程B釋放這個(gè)鎖。
Java的對(duì)象鎖和類鎖:java的對(duì)象鎖和類鎖在鎖的概念上基本上和內(nèi)置鎖是一致的,但是兩個(gè)鎖實(shí)際是有很大的區(qū)別的,對(duì)象鎖是用于對(duì)象實(shí)例方法,或者一個(gè)對(duì)象實(shí)例上的,類鎖是用于類的靜態(tài)方法或者一個(gè)類的class對(duì)象上的。
在Java中,每個(gè)對(duì)象都有一把鎖和兩個(gè)隊(duì)列,一個(gè)隊(duì)列用于掛起未獲得鎖的線程,一個(gè)隊(duì)列用于掛起條件不滿足而等待的線程。而synchronized實(shí)際上也就是一個(gè)加鎖和釋放鎖的集成。JVM負(fù)責(zé)跟蹤對(duì)象被加鎖的次數(shù)。如果一個(gè)對(duì)象被解鎖,其計(jì)數(shù)變?yōu)?。在任務(wù)(線程)第一次給對(duì)象加鎖的時(shí)候,計(jì)數(shù)變?yōu)?。每當(dāng)這個(gè)相同的任務(wù)(線程)在此對(duì)象上獲得鎖時(shí),計(jì)數(shù)會(huì)遞增。只有首先獲得鎖的任務(wù)(線程)才能繼續(xù)多次獲取該對(duì)象上的鎖。每當(dāng)任務(wù)離開一個(gè)synchronized方法,計(jì)數(shù)遞減,當(dāng)計(jì)數(shù)為0的時(shí)候,鎖被完全釋放,此時(shí)別的任務(wù)就可以使用此資源。
三、synchronized的三種應(yīng)用方式
synchronized可以修飾范圍的包括:方法級(jí)別,代碼塊級(jí)別;而實(shí)際加鎖的目標(biāo)包括:對(duì)象鎖(普通變量,靜態(tài)變量),類鎖。具體分為三種應(yīng)用方式:
1.修飾一個(gè)實(shí)例方法
被修飾的方法稱為實(shí)例同步方法,其作用范圍是整個(gè)方法,鎖定的是該方法所屬的對(duì)象(即調(diào)用該方法的對(duì)象)。所有需要獲得該對(duì)象鎖的操作都會(huì)對(duì)該對(duì)象加鎖(即訪問該對(duì)象的其他同步實(shí)例方法或進(jìn)入對(duì)該對(duì)象加鎖的代碼塊)。實(shí)例同步方法的代碼如下:
public synchronized void method(){ // 具體代碼 }
當(dāng)一個(gè)對(duì)象O1在不同的線程中執(zhí)行這個(gè)同步方法時(shí),他們之間會(huì)形成互斥,達(dá)到同步的效果。但是這個(gè)對(duì)象所屬類的另一對(duì)象O2卻能夠調(diào)用這個(gè)被加了synchronized關(guān)鍵字的方法。 每個(gè)對(duì)象實(shí)例對(duì)應(yīng)一把鎖,線程只有獲得對(duì)象實(shí)例的鎖才能執(zhí)行它的synchronized方法。 如果一個(gè)對(duì)象有多個(gè)synchronized方法,只要一個(gè)線程訪問了其中的一個(gè)synchronized方法,其它線程不能同時(shí)訪問這個(gè)對(duì)象中任何一個(gè)synchronized方法。但是該類的其他對(duì)象實(shí)例的 synchronized方法是不相干擾的。這種機(jī)制確保了同一時(shí)刻對(duì)于每一個(gè)對(duì)象實(shí)例,其所有聲明為 synchronized 的成員方法中至多只有一個(gè)處于可執(zhí)行狀態(tài)(因?yàn)橹炼嘀挥幸粋€(gè)能夠獲得該類實(shí)例對(duì)應(yīng)的鎖),從而有效避免了類成員變量的訪問沖突(只要所有可能訪問類成員變量的方法均被聲明為synchronized)。上邊的示例代碼等同于如下代碼:
public void method(){ synchronized(this){ //具體代碼 } }
其中this指的是調(diào)用這個(gè)方法的對(duì)象,如O1??梢娡椒椒▽?shí)質(zhì)是將synchronized作用于對(duì)象引用。只有獲得O1對(duì)象鎖的線程,才能夠調(diào)用O1的同步方法,而對(duì)O2而言,O1對(duì)象鎖和它互不關(guān)聯(lián),其他線程調(diào)用O2中的相同方法時(shí),并不會(huì)產(chǎn)生同步阻塞。程序也可能在這種情形下擺脫同步機(jī)制的控制,造成數(shù)據(jù)混亂。sychronized修飾方法時(shí)需要注意以下3點(diǎn):
(1)synchronized關(guān)鍵字不能繼承。
雖然可以使用synchronized來定義方法,但synchronized并不屬于方法定義的一部分,因此,synchronized關(guān)鍵字不能被繼承。如果在父類中的某個(gè)方法使用了synchronized關(guān)鍵字,而在子類中覆蓋了這個(gè)方法,在子類中的這個(gè)方法默認(rèn)情況下并不是同步的,必須顯式地在子類為這個(gè)方法加上synchronized關(guān)鍵字才可以。當(dāng)然,還可以在子類方法中調(diào)用父類中相應(yīng)的方法,這樣雖然子類中的方法不是同步的,但子類調(diào)用了父類的同步方法,因此,子類的方法也就相當(dāng)于同步了。這兩種方式的示例代碼如下:
手動(dòng)加上synchronized修飾
class Parent{ public synchronized void method() {} } class Child{ public synchronized void method() {} }
在子類中調(diào)用父類同步方法
class Parent{ public synchronized void method() {} } class Child{ public synchronized void method() {} }
(2)在定義接口方法時(shí)不能使用synchronized關(guān)鍵字。
(3)構(gòu)造方法不能使用synchronized關(guān)鍵字,但可以使用synchronized代碼塊來進(jìn)行同步。
2.修飾一個(gè)靜態(tài)方法
被修飾的方法被稱為靜態(tài)同步方法,其作用的范圍是整個(gè)靜態(tài)方法,鎖是靜態(tài)方法所屬的類(即Class對(duì)象)。所有需要獲得該類的任意對(duì)象的鎖,都會(huì)觸發(fā)同步。靜態(tài)同步方法的示例如下圖:
上述代碼中,雖然創(chuàng)建了SynThread類的兩個(gè)對(duì)象,但是該類中的run方法調(diào)用的是靜態(tài)同步方法,所以在運(yùn)行過程中會(huì)同步執(zhí)行。因此,synchronized作用在靜態(tài)方法上時(shí),可以防止多個(gè)線程同時(shí)訪問這個(gè)類中的靜態(tài)方法,它對(duì)類的所有實(shí)例對(duì)象都起作用。
3.修飾一個(gè)代碼塊
被修飾的代碼塊稱為同步語(yǔ)句塊。synchronized的括號(hào)中必須傳入一個(gè)對(duì)象(實(shí)例對(duì)象或類的Class對(duì)象)作為鎖。其作用范圍是大括號(hào){}括起來的代碼,鎖是Synchronized括號(hào)里指定的內(nèi)容。按照對(duì)象的類型可以分為類鎖和對(duì)象鎖。
(1)鎖對(duì)象為實(shí)例對(duì)象
public void method(Object o) { synchronized(o) { ... } }
上述代碼鎖定的就是o這個(gè)對(duì)象,只要進(jìn)入以該對(duì)象為鎖的任何代碼都會(huì)觸發(fā)同步。當(dāng)有一個(gè)明確的對(duì)象作為鎖時(shí),可以直接以該對(duì)象作為鎖。當(dāng)沒有明確的對(duì)象作為鎖,只是想讓一段代碼同步時(shí),可以創(chuàng)建一個(gè)特殊的對(duì)象來充當(dāng)鎖。例如:
private byte[] lock = new byte[0];
注:查看編譯后的字節(jié)碼:生成零長(zhǎng)度的byte[]對(duì)象只需3條操作碼,而Object lock = new Object()則需要7行操作碼。因此使用特殊對(duì)象來充當(dāng)鎖,大大節(jié)省了系統(tǒng)的開銷。
(2)鎖對(duì)象為類的Class對(duì)象
public class Demo{ ... public static void method(){ synchronized(Demo.class){ ... } } }
上述代碼是以Demo類的Class對(duì)象為鎖,進(jìn)入以該類任意實(shí)例對(duì)象為鎖的代碼都會(huì)觸發(fā)同步,其效果類似于靜態(tài)同步方法。
四、synchronized的實(shí)現(xiàn)原理
monitor對(duì)象
Java中的同步代碼塊是使用monitorenter和monitorexit指令實(shí)現(xiàn)的,其中monitorenter指令插入到同步代碼塊的開始位置,monitorexit指令插入到同步代碼塊的結(jié)束位置。JVM保證每一個(gè)monitorenter都有一個(gè)monitorexit與之相對(duì)應(yīng)。任何對(duì)象都有一個(gè)monitor與之相關(guān)聯(lián),當(dāng)線程執(zhí)行到monitorenter指令時(shí),將會(huì)嘗試獲取鎖對(duì)象所對(duì)應(yīng)的monitor所有權(quán),即嘗試獲取對(duì)象的鎖;當(dāng)線程執(zhí)行monitorexit指令時(shí),鎖的monitor就會(huì)被釋放。同步方法的實(shí)現(xiàn)與同步塊略有不同,它依靠的是方法修飾符上的ACC_SYNCHRONIZED實(shí)現(xiàn)。synchronized具體的實(shí)現(xiàn)原理詳見本人另一篇文章:
深入理解Java中Synchronized的實(shí)現(xiàn)原理
五、Synchronized與重入鎖ReentrantLock的區(qū)別
(1) 相對(duì)于ReentrantLock而言,synchronized鎖是重量級(jí)鎖,重量級(jí)體現(xiàn)在活躍性差一點(diǎn)。同時(shí)synchronized鎖是內(nèi)置鎖,意味著JVM能基于synchronized鎖做一些優(yōu)化:比如增加鎖的粒度(鎖粗化)、鎖消除。
(2) 在synchronized鎖上阻塞的線程是不可中斷的:線程A獲得了synchronized鎖,當(dāng)線程B也去獲取synchronized鎖時(shí)會(huì)被阻塞。而且線程B無法被其他線程中斷(不可中斷的阻塞),而ReentrantLock鎖能實(shí)現(xiàn)可中斷的阻塞。
(3) synchronized鎖釋放是自動(dòng)的,當(dāng)線程執(zhí)行退出synchronized鎖保護(hù)的同步代碼塊時(shí),會(huì)自動(dòng)釋放synchronized鎖。而ReentrantLock需要顯示地釋放:即在try-finally塊中釋放鎖。
(4) 線程在競(jìng)爭(zhēng)synchronized鎖時(shí)是非公平的:假設(shè)synchronized鎖目前被線程A占有,線程B請(qǐng)求鎖未果,被放入隊(duì)列中,線程C請(qǐng)求鎖未果,也被放入隊(duì)列中,線程D也來請(qǐng)求鎖,恰好此時(shí)線程A將鎖釋放了,那么線程D將跳過隊(duì)列中所有的等待線程并獲得這個(gè)鎖。而ReentrantLock能夠?qū)崿F(xiàn)鎖的公平性。
(5) synchronized鎖是讀寫互斥并且讀讀也互斥,ReentrantReadWriteLock 分為讀鎖和寫鎖,而讀鎖可以同時(shí)被多個(gè)線程持有,適合于讀多寫少場(chǎng)景的并發(fā)。
(6) ReentrantLock鎖的是代碼塊,synchronized還能鎖方法和類。ReentrantLock可以知道線程有沒有拿到鎖,而synchronized不能。
六、總結(jié)
(1) synchronized如果修飾的是代碼塊,則根據(jù)傳入內(nèi)容決定鎖的類型;synchronized如果修飾的是實(shí)例方法,它獲取的鎖是調(diào)用該方法的對(duì)象實(shí)例;synchronized如果修飾的是靜態(tài)方法,它獲取的鎖是調(diào)用該方法所屬的類,訪問該類所有的同步模塊都會(huì)加鎖(每個(gè)對(duì)象的同步方法、類的同步方法)。
(2) synchronized同步的關(guān)鍵要看鎖的類型
如果是對(duì)象鎖(即同步塊傳入對(duì)象作為鎖或者實(shí)例同步方法),那么其他任意需要獲得該對(duì)象鎖的同步機(jī)制都會(huì)觸發(fā)加鎖。即線程進(jìn)入一個(gè)同步實(shí)例方法,就會(huì)獲得其所屬對(duì)象的鎖。此時(shí),如果其他線程進(jìn)入該對(duì)象其他的同步實(shí)例方法或同步代碼塊時(shí)就會(huì)阻塞。
如果是類鎖(即同步塊傳入類作為鎖或者靜態(tài)同步方法),那么其他任意需要獲得該類鎖的同步機(jī)制都會(huì)觸發(fā)加鎖。即線程進(jìn)入一個(gè)靜態(tài)同步方法,就會(huì)獲得該方法所屬類的鎖。此時(shí),如果其他線程再進(jìn)入該類的其他同步方法(靜態(tài)或非靜態(tài)),或者進(jìn)入獲取該類鎖的同步塊,都會(huì)阻塞。
(3) 對(duì)象的內(nèi)置鎖和對(duì)象的狀態(tài)之間沒有內(nèi)在的關(guān)聯(lián),雖然大多數(shù)類都將內(nèi)置鎖用作一種有效的加鎖機(jī)制,但對(duì)象的域并不一定通過內(nèi)置鎖來保護(hù)。當(dāng)獲取到與對(duì)象關(guān)聯(lián)的內(nèi)置鎖時(shí),并不能阻止其他線程訪問該對(duì)象,當(dāng)某個(gè)線程獲得對(duì)象的鎖之后,只能阻止其他線程獲得同一個(gè)鎖。所以synchronized只是一個(gè)內(nèi)置鎖的加鎖機(jī)制,當(dāng)某個(gè)方法加上synchronized關(guān)鍵字后,就表明要獲得該內(nèi)置鎖才能執(zhí)行,并不能阻止其他線程訪問不需要獲得該內(nèi)置鎖的方法。
(4) 為什么要使用同步代碼塊?
首先對(duì)程序來講同步的部分很影響運(yùn)行效率,同步所覆蓋的代碼越多,對(duì)效率的影響就越嚴(yán)重。因此我們通常盡量縮小其影響范圍。所以就出現(xiàn)了同步代碼塊。只把一個(gè)方法中該同步的地方同步,并且同步代碼塊可以指定鎖對(duì)象。
(5) 使用synchronized進(jìn)入一個(gè)臨界區(qū),會(huì)獲得對(duì)應(yīng)的鎖,退出時(shí)不管是否立即使用該對(duì)象的其他同步方法,都要立即釋放鎖,重新競(jìng)爭(zhēng)獲得。如何連續(xù)使用一個(gè)對(duì)象的所有同步方法,不用中途釋放鎖,可以創(chuàng)建一個(gè)線程,使用一個(gè)同步代碼塊,同步鎖指定為該對(duì)象,然后在該代碼塊中可以連續(xù)調(diào)用該對(duì)象的同步方法。
(6) synchronized方法的缺陷
a.實(shí)例同步方法鎖定的是調(diào)用這個(gè)同步方法的對(duì)象。也就是說,一個(gè)對(duì)象P1在不同的線程中執(zhí)行其同步方法時(shí)會(huì)產(chǎn)生互斥達(dá)到同步效果。但是P1對(duì)象所屬類所創(chuàng)建的另一對(duì)象P2卻可以調(diào)用這個(gè)同步方法。同步方法實(shí)質(zhì)是將synchronized作用于對(duì)象引用。只有拿到P1對(duì)象鎖的線程,才可以調(diào)用P1的同步方法,而對(duì)P2而言,P1這個(gè)鎖與它毫不相干,程序也可能在這種情形下擺脫同步機(jī)制的控制,造成數(shù)據(jù)混亂。
b.若將一個(gè)代碼量大的方法聲明為synchronized將會(huì)大大影響效率。典型地,若將線程類的方法 run() 聲明為 synchronized ,由于在線程的整個(gè)生命期內(nèi)它一直在運(yùn)行,因此將導(dǎo)致它對(duì)本類任何 synchronized 方法的調(diào)用都不會(huì)成功。
到此這篇關(guān)于Java同步鎖synchronized用法的最全總結(jié)的文章就介紹到這了,更多相關(guān)Java synchronized的總結(jié)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring連接Mysql數(shù)據(jù)庫(kù)全過程
這篇文章主要介紹了Spring連接Mysql數(shù)據(jù)庫(kù)全過程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11mybatis連接MySQL8出現(xiàn)的問題解決方法
這篇文章主要介紹了mybatis連接MySQL8出現(xiàn)的問題解決方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-10-10MybatisPlus使用Mybatis的XML的動(dòng)態(tài)SQL的功能實(shí)現(xiàn)多表查詢
本文主要介紹了MybatisPlus使用Mybatis的XML的動(dòng)態(tài)SQL的功能實(shí)現(xiàn)多表查詢,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11Java字節(jié)流與基本數(shù)據(jù)類型的轉(zhuǎn)換實(shí)例
本篇文章主要介紹了Java字節(jié)流與基本數(shù)據(jù)類型的轉(zhuǎn)換實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Java Stream的基本概念以及創(chuàng)建方法
這篇文章主要介紹了Java Stream的基本概念以及創(chuàng)建方法,幫助大家更好的理解和學(xué)習(xí)Java,感興趣的朋友可以了解下2020-08-08MQ的分類組成優(yōu)缺點(diǎn)測(cè)試點(diǎn)入門教程
這篇文章主要為大家介紹了MQ的分類組成優(yōu)缺點(diǎn)測(cè)試點(diǎn)入門教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05IntelliJ IDEA 中使用jRebel進(jìn)行 Java 熱部署教程圖解
Rebel是一款JAVA虛擬機(jī)插件,它使得JAVA程序員能在不進(jìn)行重部署的情況下,即時(shí)看到代碼的改變對(duì)一個(gè)應(yīng)用程序帶來的影響。本文通過圖文并茂的形式給大家介紹了IntelliJ IDEA 中使用jRebel進(jìn)行 Java 熱部署教程圖解,需要的朋友參考下吧2018-04-04Java常用工具類庫(kù)——Hutool的使用簡(jiǎn)介
這篇文章主要介紹了Java常用工具類庫(kù)——Hutool的使用簡(jiǎn)介,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下2021-04-04