Java Synchronized實(shí)現(xiàn)互斥之應(yīng)用與源碼初探

前言

線程并發(fā)系列文章:

Java 線程基礎(chǔ)
Java 線程狀態(tài)
Java “優(yōu)雅”地中斷線程-實(shí)踐篇
Java “優(yōu)雅”地中斷線程-原理篇
真正理解Java Volatile的妙用
Java ThreadLocal你之前了解的可能有誤
Java Unsafe/CAS/LockSupport 應(yīng)用與原理
Java 并發(fā)"鎖"的本質(zhì)(一步步實(shí)現(xiàn)鎖)
Java Synchronized實(shí)現(xiàn)互斥之應(yīng)用與源碼初探
Java 對象頭分析與使用(Synchronized相關(guān))
Java Synchronized 偏向鎖/輕量級鎖/重量級鎖的演變過程
Java Synchronized 重量級鎖原理深入剖析上(互斥篇)
Java Synchronized 重量級鎖原理深入剖析下(同步篇)
Java并發(fā)之 AQS 深入解析(上)
Java并發(fā)之 AQS 深入解析(下)
Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 詳解
Java 并發(fā)之 ReentrantLock 深入分析(與Synchronized區(qū)別)
Java 并發(fā)之 ReentrantReadWriteLock 深入分析
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(原理篇)
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(應(yīng)用篇)
最詳細(xì)的圖文解析Java各種鎖(終極篇)
線程池必懂系列

上篇文章從無到有分析了如何實(shí)現(xiàn)"鎖"贪壳,雖然僅僅實(shí)現(xiàn)了最簡單的鎖摩瞎,但"鎖"的精華已經(jīng)提取出來了欺栗,有了這些知識新娜,本篇將分析系統(tǒng)提供的鎖-synchronized關(guān)鍵字的使用與實(shí)現(xiàn)成艘。
通過本篇文章安吁,你將了解到:

1未辆、synchronized 如何使用
2、synchronized 源碼初探
3汇四、總結(jié)

1泞莉、synchronized 如何使用

多線程訪問臨界區(qū)

由上篇文章可知,多線程訪問臨界區(qū)需要鎖:


image.png

臨界區(qū)可以是一段代碼船殉,也可以是某個(gè)方法鲫趁。

synchronized 各種使用方式

按鎖作用區(qū)域劃分,可分為兩類:

修飾方法

修飾方法又分為兩類:實(shí)例方法與靜態(tài)方法利虫。先來看看實(shí)例方法:
實(shí)例方法

public class TestSynchronized {

    //共享變量
    private int a = 0;

    public static void main(String args[]) {

        final TestSynchronized testSynchronized = new TestSynchronized();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count < 10000) {
                    testSynchronized.func1();
                    count++;
                }

                System.out.println("a = " + testSynchronized.getA() + " in thread1");
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count < 10000) {
                    testSynchronized.func1();
                    count++;
                }
                System.out.println("a = " + testSynchronized.getA() + " in thread2");
            }
        });
        t2.start();

        try {
            t1.join();
            t2.join();
            //等待t1,t2執(zhí)行完畢挨厚,再打印結(jié)果
            System.out.println("a = " + testSynchronized.getA() + " in mainThread");
        } catch (Exception e) {

        }
    }

    private synchronized void func1() {
        //修改a
        a++;
    }

    private int getA() {
        return a;
    }
}

以上兩個(gè)線程t1、t2都需要修改共享變量a的值糠惫,同時(shí)調(diào)用TestSynchronized 的對象方法: func1()進(jìn)行自增疫剃。每個(gè)線程調(diào)用func1() 10000次,循環(huán)結(jié)束后線程停止運(yùn)行硼讽。理論上每個(gè)線程都對a的值增加了10000次巢价,也就是說最后a的值應(yīng)為為:a==20000,來看看在主線程里打印a的最終值:


image.png

可以看出,多線程訪問的結(jié)果正確壤躲,說明synchronized修飾的實(shí)例方法能夠正確實(shí)現(xiàn)了多線程并發(fā)城菊。

靜態(tài)方法
再來看看靜態(tài)方法:

public class TestSynchronized {

    //共享變量
    private static int a = 0;

    public static void main(String args[]) {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count < 10000) {
                    func1();
                    count++;
                }

                System.out.println("a = " + getA() + " in thread1");
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count < 10000) {
                    func1();
                    count++;
                }
                System.out.println("a = " + getA() + " in thread2");
            }
        });
        t2.start();

        try {
            t1.join();
            t2.join();
            //等待t1,t2執(zhí)行完畢,再打印結(jié)果
            System.out.println("a = " + getA() + " in mainThread");
        } catch (Exception e) {

        }
    }

    private static synchronized void func1() {
        //修改a
        a++;
    }

    private static int getA() {
        return a;
    }
}

相對于修飾實(shí)例方法碉克,只是更改了a為static類型凌唬,并且將func1()變?yōu)殪o態(tài)方法,最終的結(jié)果與前面實(shí)例方法是一致的漏麦。
說明synchronized修飾的靜態(tài)方法能夠正確實(shí)現(xiàn)了多線程并發(fā)客税。

修飾代碼塊

synchronized 修飾方法時(shí)(靜態(tài)方法/實(shí)例方法),在進(jìn)入方法前先申請鎖撕贞,退出方法后釋放鎖更耻。假若有個(gè)方法里執(zhí)行的操作比較多,而需要并發(fā)訪問的就只有一小段捏膨,如果為了這小段臨界區(qū)將方法用synchronized修飾酥夭,那么將是大材小用。為此synchronized提供了修飾一段代碼塊的方法脊奋。
按鎖類型劃分,修飾代碼塊也分為兩類:
獲取對象鎖

    //聲明鎖對象
    private static Object object = new Object();
    private void func1() {
        //無需互斥訪問的區(qū)域
        int b = 1000;
        int c = 0;
        if (c < b) {
            c++;
        }

        //修改a
       //需要互斥訪問的區(qū)域
        synchronized (object) {
            a++;
        }
    }

可以看出雖然func1方法里有其它操作疙描,但是對于多線程操作不敏感诚隙,只有共享變量a需要互斥訪問,因此僅僅需要對操作a使用synchronized修飾起胰。
synchronized (object) 表示獲取實(shí)例對象:object的鎖久又。

獲取類鎖
再來看看如何使用類鎖:

    private void func1() {
        //無需互斥訪問的區(qū)域
        int b = 1000;
        int c = 0;
        if (c < b) {
            c++;
        }

        //修改a
        //需要互斥訪問的區(qū)域
        synchronized (TestSynchronized.class) {
            a++;
        }
    }

這次沒有實(shí)例化對象了,而是直接使用TestSynchronized.class效五,表示獲取TestSynchronized 類鎖地消。

小結(jié)

將上述關(guān)系用圖表示:


image.png

1、無論是修飾方法還是代碼塊畏妖,最終都是獲取對象鎖(類鎖是Class對象的鎖)
2脉执、實(shí)例方法與對象鎖獲取的是同一把鎖(普通對象鎖)
3、靜態(tài)方法與類鎖獲取的是同一把鎖(類鎖-Class對象鎖)

對象鎖

    private void func1() {
        synchronized (this) { }
    }

    private synchronized void func2() {
    }
    private void func3() {
    }

func1()與func2()都需要獲取對象鎖(this指的是本對象戒劫,也就是調(diào)用方法的對象本身)半夷,因此兩者的訪問是互斥的,而訪問func3()則不受影響迅细。

類鎖

    private void func1() {
        synchronized (TestSynchronized.class) { }
    }

    private static synchronized void func2() {
    }

    private static synchronized void func3() {
    }

    private static void func4() {
    }

func1()巫橄、func2()、func3()都需要獲取類鎖茵典,此處的類鎖為TestSynchronized.class 對象湘换,因此三者的訪問是互斥的,而訪問func4()則不受影響。

由此可知:

1彩倚、類鎖與對象鎖互不影響
2筹我、多線程需要獲取"同一把鎖"才能實(shí)現(xiàn)互斥

2、synchronized 源碼初探

上面的例子離不開synchronized 修飾符署恍,這是個(gè)關(guān)鍵字崎溃,JVM是如何識別這個(gè)關(guān)鍵字的呢?首先來看看synchronized編譯后的結(jié)果:

修飾代碼塊

先來看Demo:

public class TestSynchronized {

    //共享變量
    int a = 0;

    Object object = new Object();

    public static void main(String args[]) {
    }

    private void add() {
        synchronized (object) {
            a++;
        }
    }
}

以上是使用對象鎖修飾了代碼塊《⒅剩現(xiàn)在將它編譯為.class文件袁串,定位到TestSynchronized.java 文件目錄,打開命令行呼巷,輸入如下命令:

javac TestSynchronized.java

與TestSynchronized.java文件同目錄下將生成TestSynchronized.class囱修。
.class 文件肉眼看不出所以然,因此將它反編譯看看王悍,依然在同級目錄下使用如下命令:

javap -verbose -p TestSynchronized.class

然后命令行輸出一串結(jié)果破镰,當(dāng)然如果你覺得不方便查看,可以將輸出結(jié)果放在文件里压储,使用如下命令:

javap -verbose -p TestSynchronized.class > mytest.txt

來看看輸出的重點(diǎn)內(nèi)容:


image.png

上圖重點(diǎn)圈出了兩個(gè)指令:monitorenter與monitorexit鲜漩。

  • monitorenter 表示獲取鎖
  • monitorexit 表示釋放鎖
  • 兩者之間的操作就是被鎖住的臨界區(qū)
    其中monitorexit 有兩個(gè),后面一個(gè)是發(fā)生異常時(shí)會執(zhí)行

monitorenter/monitorexit 指令對應(yīng)代碼

monitorenter/monitorexit 指令對應(yīng)的代碼在哪呢集惋?
網(wǎng)上有不同的解釋孕似,我傾向于:https://github.com/farmerjohngit/myblog/issues/13 中所作的分析:

  • 在Hotspot中只用到了模板解釋器(templateTable_x86_64.cpp)
    ,字節(jié)碼解釋器(bytecodeInterpreter.cpp)根本就沒用到
  • 模板解釋器里都是匯編代碼刮刑,字節(jié)碼解釋器用的是C++實(shí)現(xiàn)的喉祭,兩者邏輯是大同小異的,為了更方便閱讀以字節(jié)碼解釋器為例

monitorenter指令對應(yīng)代碼:

image.png

在bytecodeInterpreter.cpp#1804行雷绢。

monitorexit指令對應(yīng)代碼:

image.png

在bytecodeInterpreter.cpp#1911行泛烙。

由以上可知,我們找到了monitorenter/monitorexit 指令對應(yīng)的代碼入口翘紊,也就是指令具體的實(shí)現(xiàn)位置蔽氨。

修飾方法

先來看Demo:

public class TestSynchronized {

    //共享變量
    int a = 0;

    Object object = new Object();

    public static void main(String args[]) {
    }

    private synchronized void add() {
        a++;
    }
}

同樣的使用javap指令,結(jié)果如下:


image.png

與修飾代碼塊不一樣的是:并沒有monitorenter/monitorexit 指令帆疟,但是多了ACC_SYNCHRONIZED 標(biāo)記孵滞,這個(gè)標(biāo)記是怎么解析的呢?
先看看鎖的入口和出口對應(yīng)的代碼:
方法鎖入口

image.png

在bytecodeInterpreter.cpp#643行鸯匹。
上圖標(biāo)紅的部分從名字可以看出判斷該方法是否是同步方法坊饶,若是同步方法,則進(jìn)行獲取鎖的步驟殴蓬。
尋找is_synchronized()函數(shù)匿级,在method.hpp里蟋滴。


image.png

繼續(xù)看accessFlags.hpp:


image.png

最終看jvm.h


image.png

可以看出:

用synchronized關(guān)鍵字修飾方法后,反編譯出來的代碼里帶有:ACC_SYNCHRONIZED 標(biāo)記與JVM里的JVM_ACC_SYNCHRONIZED 對應(yīng)痘绎,而這個(gè)參數(shù)最終使用的地方是通過is_synchronized()函數(shù)用來判斷是否是同步方法津函。

方法鎖出口

image.png

方法結(jié)束后運(yùn)行此段代碼,里邊判斷是否是同步方法孤页,進(jìn)而進(jìn)行釋放鎖等操作尔苦。

3、總結(jié)

synchronized修飾代碼塊和方法行施,兩者異同:

1允坚、修飾代碼塊時(shí)編譯后會在臨界區(qū)前后加入monitorenter、monitorexit 指令
2蛾号、修飾方法時(shí)進(jìn)入/退出方法時(shí)會判斷ACC_SYNCHRONIZED 標(biāo)記是否存在
3稠项、不管是用monitorenter/monitorexit 還是ACC_SYNCHRONIZED,最終都是在對象頭上做文章鲜结,都需要獲取鎖展运。

了解了synchronized使用及其源碼入口,接下來將深入探析其工作機(jī)制精刷。下篇將會分析無鎖拗胜、偏向鎖、輕量級鎖怒允、重量級鎖的實(shí)現(xiàn)機(jī)制埂软。

本文基于jdk8。

您若喜歡误算,請點(diǎn)贊、關(guān)注迷殿,您的鼓勵(lì)是我前進(jìn)的動力

持續(xù)更新中儿礼,和我一起步步為營系統(tǒng)、深入學(xué)習(xí)Java/Android

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末庆寺,一起剝皮案震驚了整個(gè)濱河市蚊夫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌懦尝,老刑警劉巖知纷,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異陵霉,居然都是意外死亡琅轧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門踊挠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乍桂,“玉大人冲杀,你說我怎么就攤上這事《米茫” “怎么了权谁?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長憋沿。 經(jīng)常有香客問我旺芽,道長,這世上最難降的妖魔是什么辐啄? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任采章,我火速辦了婚禮,結(jié)果婚禮上则披,老公的妹妹穿的比我還像新娘共缕。我一直安慰自己,他們只是感情好士复,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布图谷。 她就那樣靜靜地躺著,像睡著了一般阱洪。 火紅的嫁衣襯著肌膚如雪便贵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天冗荸,我揣著相機(jī)與錄音承璃,去河邊找鬼。 笑死蚌本,一個(gè)胖子當(dāng)著我的面吹牛盔粹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播程癌,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼舷嗡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嵌莉?” 一聲冷哼從身側(cè)響起进萄,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锐峭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沿癞,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年椎扬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了曙旭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡桂躏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出剂习,到底是詐尸還是另有隱情,我是刑警寧澤鳞绕,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站们何,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏冤竹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一鹦蠕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧在抛,春花似錦、人聲如沸刚梭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至磨德,卻和暖如春吆视,著一層夾襖步出監(jiān)牢的瞬間典挑,已是汗流浹背啦吧。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留授滓,地道東北人肆糕。 一個(gè)月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓在孝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親私沮。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容