1.2.3 Java鎖相關(guān) - synchronized詳解

Java中鎖的概念

  • 自旋鎖:為了不放棄CPU執(zhí)行事件,循環(huán)的使用CAS技術(shù)對(duì)數(shù)據(jù)嘗試進(jìn)行更新,直至成功。
  • 悲觀鎖:假定會(huì)發(fā)生并發(fā)沖突,同步所有對(duì)數(shù)據(jù)的相關(guān)操作,從讀數(shù)據(jù)就開始上鎖。
  • 樂觀鎖:假定沒有沖突,在修改數(shù)據(jù)時(shí)如果發(fā)現(xiàn)數(shù)據(jù)和之前獲取的不一致,則讀最新數(shù)據(jù),修改后重試修改。
  • 獨(dú)享鎖(寫):給資源加上寫鎖,線程可以修改資源,其他線程不能再加鎖。(單寫)
  • 共享鎖(讀):給資源加上讀鎖后只能讀不能改,其他線程也只能加讀鎖,不能加寫鎖。(多讀)
  • 可重入鎖、不可重入鎖:線程拿到一把鎖之后,可以自由進(jìn)入同一把鎖所同步的其他代碼。
  • 公平鎖、非公平鎖:爭(zhēng)搶鎖的順序,如果是按先來后到,則為公平。

幾種重要的鎖實(shí)現(xiàn)方式:Synchronized、ReentrantLock、ReentrantReadWriteLock

同步關(guān)鍵字synchronized

屬于最基本的線程通信機(jī)制,基于對(duì)象監(jiān)視器實(shí)現(xiàn)的。

Java中的每個(gè)對(duì)象都與一個(gè)監(jiān)視器相關(guān)聯(lián),一個(gè)線程可以鎖定或解鎖。

一次只有一個(gè)線程可以鎖定監(jiān)視器。

試圖鎖定該監(jiān)視器的任何其他線程都會(huì)被阻塞,直到它們可以獲得該監(jiān)視器上的鎖定為止。

特性: 可重入、獨(dú)享、悲觀鎖

鎖的范圍: 類鎖、對(duì)象鎖、鎖消除、鎖粗化。

提示:同步關(guān)鍵字,不僅是實(shí)現(xiàn)同步,根據(jù)JMM規(guī)定還能保證可見性(讀取最新主內(nèi)存數(shù)據(jù),結(jié)束后寫入主內(nèi)存)。

同步關(guān)鍵字加鎖原理

在這里插入圖片描述

同步關(guān)鍵字加鎖原理 - 輕量級(jí)鎖
在這里插入圖片描述

在這里插入圖片描述

偏向鎖到輕量級(jí)鎖

在這里插入圖片描述

重量級(jí)鎖 - 監(jiān)視器(monitor)

在這里插入圖片描述

過程解析

第一次取得鎖時(shí)會(huì)在對(duì)象的Markword標(biāo)志為偏向鎖,此時(shí)同一個(gè)用戶來使用,不用進(jìn)行取鎖操作,直到有其他的用戶也想取這個(gè)鎖,對(duì)象的Markword標(biāo)志就會(huì)升級(jí)為輕量級(jí)鎖,若上一個(gè)用戶正在使用,這個(gè)用戶自旋重鎖,自旋CAS,若沒有在使用,則直接開始競(jìng)爭(zhēng),若在并發(fā)高時(shí),上個(gè)用戶使用時(shí)間過長(zhǎng),這個(gè)用戶不斷的自旋重鎖達(dá)到一定的次數(shù)(可以自己設(shè)置),則會(huì)升級(jí)為重量級(jí)鎖,在每次自旋的時(shí)候會(huì)等待一段時(shí)間,以節(jié)約CPU資源。

鎖消除、鎖粗化

  • 鎖消除:在熱點(diǎn)代碼,運(yùn)行時(shí)Jit編譯優(yōu)化,將兩個(gè)鎖變成一個(gè)更大的鎖包含兩個(gè)內(nèi)容。
  • 鎖粗化:在熱點(diǎn)代碼,運(yùn)行時(shí)Jit編譯優(yōu)化,把鎖消除,進(jìn)入時(shí)不需要監(jiān)視器。

類鎖和對(duì)象鎖

我們都知道類的對(duì)象實(shí)例可以有很多個(gè),但是每個(gè)類只有一個(gè)class對(duì)象,所以不同對(duì)象實(shí)例的對(duì)象鎖是互不干擾的,但是每個(gè)類只有一個(gè)類鎖。

靜態(tài)方法鎖和方法鎖的區(qū)別就在于,類的每個(gè)對(duì)象靜態(tài)方法是一樣的,所以靜態(tài)方法鎖相當(dāng)于類鎖,而類的每個(gè)對(duì)象方法是不一樣的,所以方法鎖就相當(dāng)于對(duì)象鎖。

//對(duì)象鎖
public class ObjectSyncDemo1 {

    public void test1() {
        synchronized (this) {
            try {
                System.out.println(Thread.currentThread() + " 我開始執(zhí)行");
                Thread.sleep(3000L);
                System.out.println(Thread.currentThread() + " 我執(zhí)行結(jié)束");
            } catch (InterruptedException e) {
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();

        Thread.sleep(1000L); // 等1秒鐘,讓前一個(gè)線程啟動(dòng)起來
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();
    }
}
輸出結(jié)果:
Thread[Thread-0,5,main] 我開始執(zhí)行
Thread[Thread-1,5,main] 我開始執(zhí)行
Thread[Thread-0,5,main] 我執(zhí)行結(jié)束
Thread[Thread-1,5,main] 我執(zhí)行結(jié)束
//類鎖
public class ObjectSyncDemo1 {

    public void test1() {
        synchronized (ObjectSyncDemo1.class) {
            try {
                System.out.println(Thread.currentThread() + " 我開始執(zhí)行");
                Thread.sleep(3000L);
                System.out.println(Thread.currentThread() + " 我執(zhí)行結(jié)束");
            } catch (InterruptedException e) {
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();

        Thread.sleep(1000L); // 等1秒鐘,讓前一個(gè)線程啟動(dòng)起來
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();
    }
}
輸出結(jié)果:
Thread[Thread-0,5,main] 我開始執(zhí)行
Thread[Thread-0,5,main] 我執(zhí)行結(jié)束
Thread[Thread-1,5,main] 我開始執(zhí)行
Thread[Thread-1,5,main] 我執(zhí)行結(jié)束
//方法鎖
public class ObjectSyncDemo1 {
    
    public synchronized void test1() {
            try {
                System.out.println(Thread.currentThread() + " 我開始執(zhí)行");
                Thread.sleep(3000L);
                System.out.println(Thread.currentThread() + " 我執(zhí)行結(jié)束");
            } catch (InterruptedException e) {
            }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();

        Thread.sleep(1000L); // 等1秒鐘,讓前一個(gè)線程啟動(dòng)起來
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();
    }
}
輸出結(jié)果:
Thread[Thread-0,5,main] 我開始執(zhí)行
Thread[Thread-1,5,main] 我開始執(zhí)行
Thread[Thread-0,5,main] 我執(zhí)行結(jié)束
Thread[Thread-1,5,main] 我執(zhí)行結(jié)束
//靜態(tài)方法鎖
public class ObjectSyncDemo1 {

    public static synchronized void test1() {
            try {
                System.out.println(Thread.currentThread() + " 我開始執(zhí)行");
                Thread.sleep(3000L);
                System.out.println(Thread.currentThread() + " 我執(zhí)行結(jié)束");
            } catch (InterruptedException e) {
            }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();

        Thread.sleep(1000L); // 等1秒鐘,讓前一個(gè)線程啟動(dòng)起來
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();
    }
}
輸出結(jié)果:
Thread[Thread-0,5,main] 我開始執(zhí)行
Thread[Thread-0,5,main] 我執(zhí)行結(jié)束
Thread[Thread-1,5,main] 我開始執(zhí)行
Thread[Thread-1,5,main] 我執(zhí)行結(jié)束

synchronized的缺陷:當(dāng)某個(gè)線程進(jìn)入同步方法獲得對(duì)象鎖,那么其他線程訪問這里對(duì)象的同步方法時(shí),必須等待或者阻塞,這對(duì)高并發(fā)的系統(tǒng)是致命的,這很容易導(dǎo)致系統(tǒng)的崩潰。如果某個(gè)線程在同步方法里面發(fā)生了死循環(huán),那么它就永遠(yuǎn)不會(huì)釋放這個(gè)對(duì)象鎖,那么其他線程就要永遠(yuǎn)的等待。這是一個(gè)致命的問題。

當(dāng)然同步方法和同步代碼塊都會(huì)有這樣的缺陷,只要用了synchronized關(guān)鍵字就會(huì)有這樣的風(fēng)險(xiǎn)和缺陷。既然避免不了這種缺陷,那么就應(yīng)該將風(fēng)險(xiǎn)降到最低。這也是同步代碼塊在某種情況下要優(yōu)于同步方法的方面。例如在某個(gè)類的方法里面:這個(gè)類里面聲明了一個(gè)對(duì)象實(shí)例,SynObject so=new SynObject();在某個(gè)方法里面調(diào)用了這個(gè)實(shí)例的方法so.testsy();但是調(diào)用這個(gè)方法需要進(jìn)行同步,不能同時(shí)有多個(gè)線程同時(shí)執(zhí)行調(diào)用這個(gè)方法。

這時(shí)如果直接用synchronized修飾調(diào)用了so.testsy();代碼的方法,那么當(dāng)某個(gè)線程進(jìn)入了這個(gè)方法之后,這個(gè)對(duì)象其他同步方法都不能給其他線程訪問了。假如這個(gè)方法需要執(zhí)行的時(shí)間很長(zhǎng),那么其他線程會(huì)一直阻塞,影響到系統(tǒng)的性能。

如果這時(shí)用synchronized來修飾代碼塊:synchronized(so){so.testsy();},那么這個(gè)方法加鎖的對(duì)象是so這個(gè)對(duì)象,跟執(zhí)行這行代碼的對(duì)象沒有關(guān)系,當(dāng)一個(gè)線程執(zhí)行這個(gè)方法時(shí),這對(duì)其他同步方法時(shí)沒有影響的,因?yàn)樗麄兂钟械逆i都完全不一樣。

可重入鎖

public class ObjectSyncDemo2 {

    public synchronized void test1(Object arg) {
        System.out.println(Thread.currentThread() + " 我開始執(zhí)行 " + arg);
        if (arg == null) {
            test1(new Object());
        }
        System.out.println(Thread.currentThread() + " 我執(zhí)行結(jié)束" + arg);
    }

    public static void main(String[] args) throws InterruptedException {
        new ObjectSyncDemo2().test1(null);
    }
}
輸出結(jié)果:
Thread[main,5,main] 我開始執(zhí)行 null
Thread[main,5,main] 我開始執(zhí)行 java.lang.Object@31b7dea0
Thread[main,5,main] 我執(zhí)行結(jié)束java.lang.Object@31b7dea0
Thread[main,5,main] 我執(zhí)行結(jié)束null

我們可以看出synchronized是個(gè)可重入鎖,在某個(gè)線程已經(jīng)獲得某個(gè)鎖,可以再次獲取鎖而不會(huì)出現(xiàn)死鎖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容