溫馨提示×

溫馨提示×

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

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

怎么使用synchronized

發(fā)布時間:2021-10-18 14:07:18 來源:億速云 閱讀:149 作者:iii 欄目:編程語言

這篇文章主要講解了“怎么使用synchronized ”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“怎么使用synchronized ”吧!

話說 synchronized

一、前言

說起java的鎖呀,我們先想到的肯定是synchronized[?s?? kr? na? zd]了 ,這個單詞很拗口,會讀這個單詞在以后的面試中很加分(我面試過一些人 不會讀 ,他們說的是syn開頭那個單詞),不會讀略顯不專業(yè),不過問題不大,會用,懂原理才是最重要的。

內(nèi)容會由簡入難,有時候可以放棄一部分難的東西。 標(biāo)記一下 回頭再看 可能更加明朗

二、DEMO

廢話不多說,先寫hello world !!

例子: 小強(qiáng) 和 小明 同居了,但是只有一個廁所,他們每天必做的事情就是搶坑位,那么用代碼實(shí)現(xiàn)要怎么寫呢 ?

/**
 * @author 木子的晝夜
 * <p>
 * 人 實(shí)體類
 */
public class Person {
    // 名字
    private String name;
    // 上廁所
    public void gotoWc() {
        Wc.useWc(this);
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

/**
 * @author 木子的晝夜
 *
 * 廁所 實(shí)體類
 */
public class Wc {
    /**
     * 使用廁所方法
     * @param p 使用廁所的人
     */
    public static void useWc(Person p){
        try{
            System.out.println(p.getName()+" 正在使用廁所??!");
            TimeUnit.SECONDS.sleep(10);
            System.out.println(p.getName()+" 用完了??!");
        } catch (Exception e) {
            // 廁所萬一壞了 也得結(jié)束使用
            System.out.println(p.getName()+" 用完了??!");
        }
    }
}


/**
 * @author 木子的晝夜
 * 這個測試 是小強(qiáng)與小明商量好了 說小強(qiáng)你先來   小強(qiáng)完事兒了  小明再來  
 * 這個不會發(fā)生沖突 因為是商量好的 順序執(zhí)行 
 * 大家都知道  順序是不會出什么問題的 
 */
public class SyncTest {
    public static void main(String[] args) {
        // 小強(qiáng)對象
        Person xiaoqiang = new Person();
        xiaoqiang.setName("小強(qiáng)");

        // 小明對象
        Person xiaoming = new Person();
        xiaoming.setName("小明");

        // 上廁所
        xiaoqiang.gotoWc();
        xiaoming.gotoWc();
    }

}

上廁所過程:

怎么使用synchronized

如果倆人沒商量,自己去自己的呢 ?

/**
 * @author 木子的晝夜
 */
public class SyncTest02 {
    public static void main(String[] args) {
        // 小強(qiáng)對象
        Person xiaoqiang = new Person();
        xiaoqiang.setName("小強(qiáng)");

        // 小明對象
        Person xiaoming = new Person();
        xiaoming.setName("小明");

        // 開啟兩個線程 誰也不理誰 自己干自己的 
        new Thread(()->xiaoqiang.gotoWc()).start();
        new Thread(()->xiaoming.gotoWc()).start();
    }

}

上廁所過程:

怎么使用synchronized

上圖很明顯可以看出來,小強(qiáng)沒上完呢,小明就去上了,要是小的還湊活,大的怎么辦? 畫面自己想~~

這個時候大家可能想到了,廁所門上沒鎖嗎? 誰先進(jìn)去鎖住不就行了嗎?

怎么使用synchronized 答對了!

/**
 * @author 木子的晝夜
 *
 * 改造后 廁所 實(shí)體類
 */
public class Wc {
    /**
     * 使用廁所方法
     * synchronized: 誰先進(jìn)廁所 馬上上鎖 ??!
     * @param p 使用廁所的人
     */
    public static synchronized void useWc(Person p){
        try{
            System.out.println(p.getName()+" 正在使用廁所??!");
            TimeUnit.SECONDS.sleep(10);
            System.out.println(p.getName()+" 用完了!!");
        } catch (Exception e) {
            // 廁所萬一壞了 也得結(jié)束使用
            System.out.println(p.getName()+" 用完了??!");
        }
    }
}

/**
 * @author 木子的晝夜
 */
public class SyncTest02 {
    public static void main(String[] args) {
        // 小強(qiáng)對象
        Person xiaoqiang = new Person();
        xiaoqiang.setName("小強(qiáng)");

        // 小明對象
        Person xiaoming = new Person();
        xiaoming.setName("小明");

        // 開啟兩個線程 誰也不理誰 自己干自己的  但是這次廁所有鎖,
        // 誰先進(jìn)去 就把鎖鎖住 
        new Thread(()->xiaoqiang.gotoWc()).start();
        new Thread(()->xiaoming.gotoWc()).start();
    }

}

上廁所過程:

怎么使用synchronized

可以看到,小強(qiáng)先上完,小明再上的,這樣就不會出什么問題了。

有人可能會問了,只見上鎖,沒有解鎖,小明怎么進(jìn)去的?

這就是synchronized的一個特性了,它會自動釋放鎖, synchronized包裹的代碼執(zhí)行完之后,鎖就自動釋放了。

所以避免了忘記釋放鎖,帶來的尷尬~

三、 假裝學(xué)術(shù)討論

3.1 為什么要上鎖?

以廁所為例,自己想去吧。 提出:”共享資源“ 這個詞就對了

3.2 對象鎖 類鎖

1) 對象鎖 顧名思義 就是鎖一個對象

/**
 * @author 木子的晝夜
 * 對象鎖
 */
public class SyncObject {

    /**
     * 累加值 (共享資源)
     */
     int count = 0;
    /**
     * 鎖對象
     */
    private Object lock = new Object();

    public static void main(String[] args) {
        SyncObject so = new SyncObject();

        // 線程1
        new Thread(()->{
            try {
                for (;;){
                    TimeUnit.SECONDS.sleep(2);
                    so.increaseCount();
                }
            } catch (Exception e) {
                System.err.println("錯誤");
            }
        },"線程1").start();

        // 線程2
        new Thread(()->{
            try {
                for (;;){
                    TimeUnit.SECONDS.sleep(2);
                    so.increaseCount();
                }
            } catch (Exception e) {
                System.err.println("錯誤");
            }
        },"線程2").start();
    }

    /**
     * count累加
     */
    public void increaseCount(){
        // 加鎖
        synchronized (lock){
            count = count+1;
            System.out.println(Thread.currentThread().getName()+" count="+count);
        }
    }
}

怎么使用synchronized

例子中:兩個線程增加count ,鎖的是lock這個對象 ,這就叫對象鎖 。

有時候會看見synchronized(this) 這是什么鎖 ? this嘛 就是指當(dāng)前對象,也是對象鎖,

synchronized(this) 相當(dāng)于 在方法上加synchronized,下邊這兩個方法都是鎖的當(dāng)前對象

	 /**
     * count累加
     */
    public void increaseCount(){
        // 加鎖
        synchronized (this){
            count = count+1;
            System.out.println(Thread.currentThread().getName()+" count="+count);
        }
    }

    /**
     * count累加  加鎖
     */
    public synchronized void  increaseCount02(){
            count = count+1;
            System.out.println(Thread.currentThread().getName()+" count="+count);
    }

2) 類鎖 顧名思義 就是給一個類加鎖

每個類load到內(nèi)存之后呢,會生成一個Class類型的對象 鎖的就是他

其實(shí)也是鎖一個對象 只是這個對象比較特殊,它代表類

/**
 * @author 木子的晝夜
 * 對象鎖
 */
public class SyncObject03 {

    /**
     * 累加值 (共享資源)
     */
    static int count = 0;
    /**
     * 鎖對象
     */
    private Object lock = new Object();

    public static void main(String[] args) {
        SyncObject03 so = new SyncObject03();

        // 線程1
        new Thread(()->{
            for (;;){
                SyncObject03.increaseCount();
            }
        },"線程1").start();
        
        // 線程2
        new Thread(()->{
            for (;;){
                SyncObject03.increaseCount();
            }
        },"線程2").start();

    }

    /**
     * count累加
     */
    public synchronized static void increaseCount(){
        // 加鎖
        try {
            count = count+1;
            System.out.println(Thread.currentThread().getName()+" count="+count);
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e){
            System.err.println("錯誤~");
        }
    }

    /**
     * count累加
     */
    public   void increaseCount02(){
        synchronized(SyncObject03.class){
            // 加鎖
            try {
                count = count+1;
                System.out.println(Thread.currentThread().getName()+" count="+count);
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e){
                System.err.println("錯誤~");
            }
        }
    }

}

以上兩種方式 都是類鎖。

3.3 上鎖方法執(zhí)行的時候 可以執(zhí)行當(dāng)前對象未上鎖方法嗎?

這是一個用腳指頭就能想到的答案,但是好多面試官問。 問了之后呢 你就蒙了~~ 難道不能?

答案是:能!

為什么能呢?因為愛所以愛~~ 錯了,重來 .. 因為能所以能~~

小明在吃飯,給碗上個鎖,別人不能用, 那小明能同時看他的偶像鄧紫棋唱歌嗎 ?

誰要是說不可以,以后吃飯不讓他玩手機(jī)、pad、電腦 。 就讓他吃吃吃 (那是豬)

怎么使用synchronized

/**
 * @author 木子的晝夜
 *  可能出現(xiàn)的面試題 
 */
public class SyncObject04 {


    private Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        SyncObject04 so = new SyncObject04();

        // 線程1
        new Thread(()->{
            so.increaseCount();
        },"線程1").start();

        Thread.sleep(2000);

        // 線程2
        new Thread(()->{
            so.lookMv();
        },"線程2").start();
    }

    /**
     * 吃飯
     */
    public void increaseCount(){
        // 加鎖
        synchronized (this){
            try{
                System.out.println("吃飯 ");
                // 也可以在吃飯這里 跟妹子聊天 
                chatWithGirl();
                TimeUnit.SECONDS.sleep(10);
            } catch (Exception e) {
                System.err.println("飯掉地上了~");
            }
            System.out.println("吃完飯 ");
        }
    }

    /**
     * 看演唱會視頻
     */
    public  void  lookMv(){
        System.out.println("看演唱會視頻");
    }
    
    /**
     * 看跟美女聊天
     */
    public  void  chatWithGirl(){
        System.out.println("跟美女聊天");
    }
}

怎么使用synchronized

3.4 可重入

能一句話總結(jié)嗎?

咳咳: 同一線程可以調(diào)用加了同一把鎖的兩個方法 不會阻塞。

例子:同一個人可以用同一雙筷子(筷子加鎖),吃不同的菜~~

吃魚的時候獲取了鎖,在吃魚方法里調(diào)用吃沙拉方法,是可以調(diào)用成功了 因為兩個方法用的同一把鎖

/**
 * @author 木子的晝夜
 */
public class TestC {
    /**
     * 一雙筷子 只能一個人同一時間使用(一個線程)
     */
    Object chopsticks   = new Object();

    public static void main(String[] args) {
        TestC c = new TestC();
        new Thread(()->{
            c.eatFish();
        }).start();

    }

    /**
     * 吃
     */
    public void eatFish( ) {
        synchronized (chopsticks) {
           try {
              System.out.println("吃 魚");
              Thread.sleep(2000);
              eatSalad();
           } catch (Exception e){ }
        }
    }

    /**
     * 吃沙拉
     */
    public void eatSalad() {
        synchronized (chopsticks) {
            try {
                System.out.println("吃 沙拉");
                Thread.sleep(2000);
                eatFish();
            } catch (Exception e){ }
        }
    }
}

3.5 底層實(shí)現(xiàn)

(1) 簡單版

jdk1.6之前 synchronized 是 重量級鎖 什么是重量級鎖? 就是每次鎖都會去找操作系統(tǒng)申請鎖。

jdk1.6及以后改進(jìn)為鎖升級

簡單思路是:

synchronized(object)

  1. 線程A 第一個訪問

  2. 偏向鎖 只在object的markword 中記錄線程A的線程ID

  3. 如果線程A 又進(jìn)來訪問 一看markword的線程號是自己 那就直接用

  4. 這時候線程B 來了 ,線程B 一看我擦? 有人占用了鎖!

  5. 線程B 會循環(huán)死等 ,類似在廁所門口,敲敲門問問線程A 你好了嗎?敲敲門問問線程A 你好了嗎?敲敲門問問線程A 你好了嗎?敲敲門問問線程A 你好了嗎?

  6. 線程B的這一操作,用術(shù)語將叫: 自旋鎖

  7. 線程B 問10次之后,得不到鎖,就會升級為重量級鎖 (去操作系統(tǒng)申請資源)

  8. 無鎖->偏向鎖->自旋鎖->重量級鎖

  9. 鎖 一般 。。 只升級 不降級

(2)復(fù)雜版
  1. CAS 簡單敘述 了解入門

怎么使用synchronized

什么是ABA問題,假如你有媳婦兒,我說假如~ 以偷零花錢為例

https://www.processon.com/view/link/603c96ca07912913b4f2c55f

怎么使用synchronized

這是一個故事: 小強(qiáng)偷零花錢請小明吃飯的故事

ABA 就是:

小強(qiáng)媳婦兒 出門看的錢是10萬 (A)

小強(qiáng)偷拿1萬 剩余9萬(B)

小強(qiáng)找小月借了1萬 放回去 總共10萬(A)

小強(qiáng)媳婦兒回來 一看是10萬,很滿意。 但是 她不知道 這是偷梁換柱啊

后來小強(qiáng)媳婦兒看了我的博客 , 發(fā)現(xiàn)了秘密 ,她應(yīng)該怎么解決呢 ?

關(guān)鍵字:version 版本號

她上班之前,在家里的存款上用筆寫了一個版本,小強(qiáng)如果再偷錢,再還回來,這個版本就變了(+1)

這樣就解決了ABA問題

2. 上鎖過程

怎么使用synchronized

重度競爭: 耗時過長 自選過多 wait等

新建對象可能直接是匿名偏向 ( 如果默認(rèn)開啟了偏向鎖) ,因為沒有偏向任何一個線程,所以是匿名偏向

JVM默認(rèn)不開啟 延遲4秒后才會開啟 偏向鎖

3. new 一個對象 長什么樣 ? markword 是啥

怎么使用synchronized

1. 查看工具 : JOL (Java Object Layout )

直接maven引入就可以使用了

<dependencies>
    <dependency>
        <groupId>org.openjdk.jol</groupId>
        <artifactId>jol-core</artifactId>
        <version>0.9</version>
    </dependency>
</dependencies>
/**
 * @author 木子的晝夜
 */
public class Person {
    long  money;

    public long getMoney() {
        return money;
    }

    public void setMoney(long money) {
        this.money = money;
    }
}

/**
 * @author 木子的晝夜
 */
public class JolTest {
    public static void main(String[] args) {
        Person p = new Person();
        System.out.println(ClassLayout.parseInstance(p).toPrintable());
    }
}

// 輸出結(jié)果 
Person object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 
      4     4        (object header)                           00 00 00 00 
      8     4        (object header)                           43 c1 00 f8 
     12     4        (alignment/padding gap)                  
     16     8   long Person.money                              0
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

怎么使用synchronized

咦? 不是要寫synchronized 嗎 ?

2. markword

我給對象p 加鎖,然后輸出 layout 可以發(fā)現(xiàn) markword 改變了

所以呢~~ 鎖信息 是記錄再markword中的

/**
 * @author 木子的晝夜
 */
public class JolTest {
    public static void main(String[] args) {
        Person p = new Person();
        System.out.println(ClassLayout.parseInstance(p).toPrintable());

        synchronized (p) {
            System.out.println(ClassLayout.parseInstance(p).toPrintable());
        }
    }
}

怎么使用synchronized

markword 這么厲害嗎? 不 ! 它還能更厲害 。 我們看一下 它里邊都記錄了一些什么信息

先暫時看最后3bit 其他 不是很了解 這個時候可以對比上邊layout的輸出 看一下

剛開始是01 加鎖之后變成了00 (沒有開啟偏向鎖 直接到輕量級鎖)

怎么使用synchronized

怎么使用synchronized

先看最后2bit:


00:輕量級鎖 自旋鎖

自旋鎖,耗CPU資源 是在用戶態(tài)操作 不關(guān)聯(lián)內(nèi)核態(tài)

兩個線程 爭著把自己的Lock Record 放到markword中

誰先放進(jìn)去,誰先獲得鎖,另一個人接著cas 去放

怎么使用synchronized

Lock Record 指向的是什么呢 是無鎖狀態(tài)的markword

怎么使用synchronized

這就解釋了 為什么hashcode不丟失的問題 因為有備份記錄

這里鎖重入: 上邊提到了,鎖重入 ,鎖每進(jìn)一次,都會加一個LR 從第二個LR開始 指向的就是一個null

等鎖退出 也就是monitorexit(鎖代碼塊執(zhí)行完 或 拋異常)的時候LR -1 ,LR -1 ,LR -1 一直減 ,退一次減一次


10:重量級鎖

向OS 申請鎖,進(jìn)了內(nèi)核態(tài) , c++ 新建了一個object monitor對象 markword中放的就是這個 指針 (java中就是個地址或者是ID )

重量級鎖,都在一個隊列里等著,比較不消耗CPU資源

可重入鎖: 重量級是記錄再object moniter 的某個屬性上

什么時候自選上升為重量級鎖:

1. 自選次數(shù)超過10次  或者 自選的線程數(shù)超過CPU的一半  
2. jdk1.6之前 -XX:PreBlockSpin可以調(diào)整 自選超過多少次升級 
3. jdk1.6之后加入了自適應(yīng) Adapative Self Sping  JVM自己個兒控制

11:GC回收標(biāo)記


01: 再看倒數(shù)第三位

0:無鎖

1:偏向鎖 放線程ID , c++實(shí)現(xiàn)是用的指針

3. synchronized 編譯成字節(jié)碼 會有兩個單詞 monitorenter monitorexit

什么時候monitorexit呢 , 代碼執(zhí)行完 ,或者是異常發(fā)生 這就是synchronized 自動釋放鎖的原理

4. 為什么有自旋鎖還需要重量級鎖

(1) 自旋是消耗CPU資源的,如果鎖的時間長,或者自旋線程多,CPU會被大量消耗

(2) 重量級鎖有等待隊列,所有拿不到鎖的進(jìn)入等待隊列,不需要消耗CPU資源

5. 偏向鎖是否一定比自旋鎖效率高?

(1)不一定 當(dāng)你知道肯定存在多線程競爭的時候,偏向鎖會涉及鎖撤銷,這時候自旋鎖會比較好一點(diǎn)

(2) JVM啟動過程就會很很多線程競爭,所以默認(rèn)不開啟偏向鎖,過一段時間才會開啟

(3) -XX:BiasedLockingStartupDelay = 0 默認(rèn)是 4 秒

感謝各位的閱讀,以上就是“怎么使用synchronized ”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對怎么使用synchronized 這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!

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

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

AI