溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java同步鎖synchronized怎么使用

發(fā)布時(shí)間:2023-03-21 16:44:07 來源:億速云 閱讀:131 作者:iii 欄目:開發(fā)技術(shù)

本文小編為大家詳細(xì)介紹“Java同步鎖synchronized怎么使用”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“Java同步鎖synchronized怎么使用”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識(shí)吧。

    一、并發(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)同步方法的示例如下圖:

    Java同步鎖synchronized怎么使用

    上述代碼中,雖然創(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è)代碼塊


    被修飾的代碼塊稱為同步語句塊。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不能。

    讀到這里,這篇“Java同步鎖synchronized怎么使用”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI