您好,登錄后才能下訂單哦!
這篇文章給大家介紹Java中怎么利用synchronized實(shí)現(xiàn)多線程鎖,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
修飾代碼塊:被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號(hào){}括起來的代碼,作用于調(diào)用對象
修飾方法:被修飾的方法稱為同步方法,其作用的范圍是整個(gè)方法,作用于調(diào)用對象 > 注意:synchronized修飾方法時(shí)必須是顯式調(diào)用,如果沒有顯式調(diào)用,例如子類重寫該方法時(shí)沒有顯式加上synchronized,則不會(huì)有加鎖效果。
修飾靜態(tài)方法:其作用的范圍是整個(gè)靜態(tài)方法,作用于所有對象
修飾類:其作用的范圍是synchronized后面括號(hào)括起來的部分(例如:test.class),作用于所有對象
對象鎖:Java的所有對象都含有1個(gè)互斥鎖,這個(gè)鎖由JVM自動(dòng)獲取和釋放。線程進(jìn)入synchronized方法的時(shí)候獲取該對象的鎖,當(dāng)然如果已經(jīng)有線程獲取了這個(gè)對象的鎖,那么當(dāng)前線程會(huì)等待;synchronized方法正常返回或者拋異常而終止,JVM會(huì)自動(dòng)釋放對象鎖。這里也體現(xiàn)了用synchronized來加鎖的1個(gè)好處,方法拋異常的時(shí)候,鎖仍然可以由JVM來自動(dòng)釋放。
類鎖:對象鎖是用來控制實(shí)例方法之間的同步,類鎖是用來控制靜態(tài)方法(或靜態(tài)變量互斥體)之間的同步。其實(shí)類鎖只是一個(gè)概念上的東西,并不是真實(shí)存在的,它只是用來幫助我們理解鎖定實(shí)例方法和靜態(tài)方法的區(qū)別的。java類可能會(huì)有很多個(gè)對象,但是只有1個(gè)Class對象,也就是說類的不同實(shí)例之間共享該類的Class對象。Class對象其實(shí)也僅僅是1個(gè)java對象,只不過有點(diǎn)特殊而已。由于每個(gè)java對象都有1個(gè)互斥鎖,而類的靜態(tài)方法是需要Class對象。所以所謂的類鎖,不過是Class對象的鎖而已。
類鎖和對象鎖不是同1個(gè)東西,一個(gè)是類的Class對象的鎖,一個(gè)是類的實(shí)例的鎖。也就是說:1個(gè)線程訪問靜態(tài)synchronized的時(shí)候,允許另一個(gè)線程訪問對象的實(shí)例synchronized方法。反過來也是成立的,因?yàn)樗麄冃枰逆i是不同的。
對應(yīng)的實(shí)驗(yàn)代碼如下:
@Slf4j public class SynchronizedExample { // 修飾一個(gè)代碼塊 public void test1(int j) { synchronized (this) { for (int i = 0; i < 10; i++) { log.info("test1 {} - {}", j, i); } } } // 修飾一個(gè)方法 public synchronized void test2(int j) { for (int i = 0; i < 10; i++) { log.info("test2 {} - {}", j, i); } } // 修飾一個(gè)類 public static void test3(int j) { synchronized (SynchronizedExample.class) { for (int i = 0; i < 10; i++) { log.info("test3 {} - {}", j, i); } } } // 修飾一個(gè)靜態(tài)方法 public static synchronized void test4(int j) { for (int i = 0; i < 10; i++) { log.info("test4 {} - {}", j, i); } } public static void main(String[] args) { SynchronizedExample example1 = new SynchronizedExample(); SynchronizedExample example2 = new SynchronizedExample(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> { example1.test2(1); }); executorService.execute(() -> { example2.test2(2); }); } }
在JDK1.6之前,synchronized一直被稱呼為重量級(jí)鎖(重量級(jí)鎖就是采用互斥量來控制對資源的訪問)。通過反編譯成字節(jié)碼指令可以看到,synchronized會(huì)在同步塊的前后分別形成monitorenter和monitorexit這兩個(gè)字節(jié)碼指令。根據(jù)虛擬機(jī)規(guī)范的要求,在執(zhí)行monitorenter指令時(shí),首先要嘗試獲取對象的鎖。如果這個(gè)對象沒被鎖定,或者當(dāng)前線程已經(jīng)擁有了那個(gè)對象的鎖,把鎖的計(jì)數(shù)器加1,相應(yīng)的,在執(zhí)行monitorexit指令時(shí)會(huì)將鎖計(jì)算器減1,當(dāng)計(jì)數(shù)器為0時(shí),鎖就被釋放,然后notify通知所有等待的線程。 Java的線程是映射到操作系統(tǒng)的原生線程上的,如果要阻塞或喚醒一個(gè)線程,都需要操作系統(tǒng)來幫忙完成,這就需要用戶態(tài)和內(nèi)核態(tài)切換,大量的狀態(tài)轉(zhuǎn)換需要耗費(fèi)很多處理器的時(shí)間。
在JDK1.6中對鎖的實(shí)現(xiàn)引入了大量的優(yōu)化:
鎖粗化(Lock Coarsening):將多個(gè)連續(xù)的鎖擴(kuò)展成一個(gè)范圍更大的鎖,用以減少頻繁互斥同步導(dǎo)致的性能損耗。
鎖消除(Lock Elimination):JVM即時(shí)編譯器在運(yùn)行時(shí),通過逃逸分析,如果判斷一段代碼中,堆上的所有數(shù)據(jù)不會(huì)逃逸出去從來被其他線程訪問到,就可以去除這個(gè)鎖。
偏向鎖(Biased Locking):目的是消除數(shù)據(jù)無競爭情況下的同步原語。使用CAS記錄獲取它的線程。下一次同一個(gè)線程進(jìn)入則偏向該線程,無需任何同步操作。
適應(yīng)性自旋(Adaptive Spinning):為了避免線程頻繁掛起、恢復(fù)的狀態(tài)切換消耗。線程會(huì)進(jìn)入自旋狀態(tài)。JDK1.6引入了自適應(yīng)自旋。自旋時(shí)間根據(jù)之前鎖自旋時(shí)間和線程狀態(tài),動(dòng)態(tài)變化,可以能減少自旋的時(shí)間。
輕量級(jí)鎖(Lightweight Locking):在沒有多線程競爭的情況下避免重量級(jí)互斥鎖,只需要依靠一條CAS原子指令就可以完成鎖的獲取及釋放。
在JDK1.6之后,synchronized不再是重量級(jí)鎖,鎖的狀態(tài)變成以下四種狀態(tài):
無鎖->偏向鎖->輕量級(jí)鎖->重量級(jí)鎖
大部分時(shí)候,共享數(shù)據(jù)的鎖定狀態(tài)只會(huì)持續(xù)很短的一段時(shí)間,為了這段時(shí)間去掛起和恢復(fù)線程并不值得。如果能讓兩個(gè)或以上的線程同時(shí)并行執(zhí)行,我們就可以讓后面請求鎖的那個(gè)線程“稍等一下”,但不放棄處理器的執(zhí)行時(shí)間,看看持有鎖的線程是否很快就會(huì)釋放鎖。這項(xiàng)技術(shù)就是所謂的自旋鎖。
自旋等待的時(shí)間必須要有一定的限度,如果自旋超過了限定的次數(shù)仍然沒有獲取到鎖,則該線程應(yīng)該被掛起。在JDK1.6中引入了自適應(yīng)的自旋鎖,自適應(yīng)意味著自旋的時(shí)間不再固定,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來決定。
> 所謂自旋,不是獲取不到就阻塞,而是在原地等待一會(huì)兒,再次嘗試(當(dāng)然次數(shù)或者時(shí)長有限),他是以犧牲CPU為代價(jià)來換取內(nèi)核狀態(tài)切換帶來的開銷。借助于適應(yīng)性自旋,可以在CPU時(shí)間片的損耗和內(nèi)核狀態(tài)的切換開銷之間相對的找到一個(gè)平衡,進(jìn)而能夠提高性能
大多數(shù)情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價(jià)更低而引入了偏向鎖。當(dāng)一個(gè)線程訪問同步塊并獲取鎖時(shí),會(huì)在對象頭的鎖記錄里存儲(chǔ)鎖偏向的線程ID,以后該線程在進(jìn)入和退出同步塊時(shí)不需要進(jìn)行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的MarkWord里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖。如果測試成功,表示線程已經(jīng)獲得了鎖。如果測試失敗,則需要再測試一下MarkWord中偏向鎖的標(biāo)識(shí)是否設(shè)置成1(表示當(dāng)前是偏向鎖):如果沒有設(shè)置,則使用CAS競爭鎖;如果設(shè)置了,則嘗試使用CAS將對象頭的偏向鎖指向當(dāng)前線程,如果失敗則進(jìn)行輕量鎖的升級(jí)。
如果說偏向鎖是只允許一個(gè)線程獲得鎖,那么輕量級(jí)鎖就是允許多個(gè)線程獲得鎖,但是只允許他們順序拿鎖,不允許出現(xiàn)競爭,也就是拿鎖失敗的情況,輕量級(jí)鎖的步驟如下:
線程1在執(zhí)行同步代碼塊之前,JVM會(huì)先在當(dāng)前線程的棧幀中創(chuàng)建一個(gè)空間用來存儲(chǔ)鎖記錄,然后再把對象頭中的MarkWord復(fù)制到該鎖記錄中,官方稱之為DisplacedMarkWord。然后線程嘗試使用CAS將對象頭中的MarkWord替換為指向鎖記錄的指針。如果成功,則獲得鎖,進(jìn)入步驟3)。如果失敗執(zhí)行步驟2)
線程自旋,自旋成功則獲得鎖,進(jìn)入步驟3)。自旋失敗,則膨脹成為重量級(jí)鎖,并把鎖標(biāo)志位變?yōu)?0,線程阻塞進(jìn)入步驟3)
鎖的持有線程執(zhí)行同步代碼,執(zhí)行完CAS替換MarkWord成功釋放鎖,如果CAS成功則流程結(jié)束,CAS失敗執(zhí)行步驟4)
CAS執(zhí)行失敗說明期間有線程嘗試獲得鎖并自旋失敗,輕量級(jí)鎖升級(jí)為了重量級(jí)鎖,此時(shí)釋放鎖之后,還要喚醒等待的線程
自旋的線程在自旋過程中,成功獲得資源(即之前獲的資源的線程執(zhí)行完成并釋放了共享資源),則整個(gè)狀態(tài)依然處于輕量級(jí)鎖的狀態(tài),如果自旋失敗則進(jìn)入重量級(jí)鎖的狀態(tài),這個(gè)時(shí)候,自旋的線程進(jìn)行阻塞,等待之前線程執(zhí)行完成并喚醒自己,需要從用戶態(tài)切換到內(nèi)核態(tài)實(shí)現(xiàn)。(當(dāng)競爭競爭激烈時(shí),線程直接進(jìn)入阻塞狀態(tài)。不過在高版本的JVM中不會(huì)立刻進(jìn)入阻塞狀態(tài)而是會(huì)自旋一小會(huì)兒看是否能獲取鎖如果不能則進(jìn)入阻塞狀態(tài)。)
可以簡單總結(jié)是如下場景:
只有一個(gè)線程進(jìn)入加鎖區(qū),鎖狀態(tài)是偏向鎖
多個(gè)線程交替進(jìn)入加鎖區(qū),鎖狀態(tài)可能是輕量級(jí)鎖
多線程同時(shí)進(jìn)入加鎖區(qū),鎖狀態(tài)可能是重量級(jí)鎖
關(guān)于Java中怎么利用synchronized實(shí)現(xiàn)多線程鎖就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。