java synchronized作用原理

1. synchronized簡(jiǎn)介

synchronized作為java關(guān)鍵字,是一種多線程同步的手段《裕可以保證資源在多線程共享的情況下的正確性恼策。舉反例鸦致,如下代碼就是線程不安全的:

class Unsafe {

  int count = 0;

  public void increment() {
      count++;
  }

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

      for (int i = 0; i < 100; i++) {
          Thread thread = new Thread(() -> {
              try {
                  Thread.sleep(1);
                  unsafe.increment();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          });
          thread.start();
      }

      Thread.currentThread().sleep(1000);
      System.out.println(unsafe.count);
  }
}

啟動(dòng)100個(gè)線程對(duì)共享的變量count進(jìn)行+1操作,但是輸出結(jié)果并不是想象中的100,有可能是99、98分唾、97等抗碰,但是如果對(duì)方法increment()加上synchronized進(jìn)行修飾,程序就能表現(xiàn)正常的輸出結(jié)果100绽乔。為什么synchronized會(huì)有這樣的魔力弧蝇?其原理是什么樣子的呢?

2.從java虛擬機(jī)內(nèi)存層次簡(jiǎn)述例子的執(zhí)行過(guò)程

JVM是以進(jìn)程為單位的折砸,所有的同步討論范疇都是在多線程的范圍內(nèi)看疗。
就以上面的栗子作為案例來(lái)敘述,先看下內(nèi)存結(jié)構(gòu)圖


簡(jiǎn)單描述下單個(gè)線程進(jìn)行方法調(diào)用的時(shí)候執(zhí)行步驟:
1. 線程調(diào)用increment方法睦授,讀取Heap中count的值鹃觉,并將count拷貝一個(gè)副本到當(dāng)前堆棧中。
2. 將副本中的count值加1睹逃。
3. 將副本中的count值重新放到Heap中盗扇。

每次加1的操作都包含了3個(gè)具體的步驟,很明顯不是原子操作沉填,在多線程環(huán)境下肯定存在線程安全性的問(wèn)題疗隶。

如果在increment方法上加上synchronized修飾,則執(zhí)行過(guò)程變成如下的過(guò)程:
1. 搶占unsafe對(duì)象的Monitor lock(監(jiān)視器鎖翼闹,見(jiàn)下面的詳解)
2. 搶占到鎖后執(zhí)行上面1~3的步驟斑鼻,搶占不到則進(jìn)入阻塞狀態(tài),等待監(jiān)視器鎖的釋放猎荠,再次進(jìn)行監(jiān)視器鎖的搶占坚弱。

3. 監(jiān)視器鎖

在JAVA世界中萬(wàn)物皆對(duì)象,每個(gè)對(duì)象都提供了與之相關(guān)的監(jiān)視器鎖关摇。java虛擬機(jī)可以支持方法級(jí)的同步和方法內(nèi)部一段指令序列的同步荒叶,這兩種方式的同步結(jié)構(gòu)都是使用監(jiān)視器鎖來(lái)支持的。

1. 方法級(jí)的同步

class Unsafe {

    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public static void main(String[] args) {
        Unsafe unsafe = new Unsafe();
        unsafe.increment();
    }
}

方法級(jí)的同步是隱式的输虱,即無(wú)需通過(guò)字節(jié)碼指令來(lái)控制些楣。虛擬機(jī)可以從方法常量池的方法表結(jié)構(gòu)中的ACC_SYNCHRONIZED訪問(wèn)標(biāo)志得知一個(gè)方法是否聲明為同步方法。當(dāng)方法調(diào)用時(shí)宪睹,調(diào)用指令將會(huì)檢查方法的ACC_SYNCHRONIZED訪問(wèn)標(biāo)志是否被設(shè)置愁茁,如果設(shè)置了,則要求當(dāng)前線程現(xiàn)持有當(dāng)前監(jiān)視器鎖亭病,退出時(shí)(不管是正常退出還是異常退出)則是否當(dāng)前持有的監(jiān)視器鎖鹅很。應(yīng)用到如上代碼中,則在main線程調(diào)用unsafe.increment()方法時(shí)則需要首先獲取當(dāng)前對(duì)象監(jiān)視器鎖罪帖,在退出方法執(zhí)行時(shí)則釋放持有的監(jiān)視器鎖促煮。

2. 方法內(nèi)部同步

public class Unsafe {

    private int count = 0;

    public void increment() {
        synchronized (this) {
            count++;
        }
    }

    public static void main(String[] args) {
        Unsafe unsafe = new Unsafe();
        unsafe.increment();
    }
}

通過(guò)javap工具反編譯出increment方法相關(guān)的字節(jié)碼
public void increment();
    Code:
       0: aload_0
       1: dup
       2: astore_1
       3: monitorenter
       4: aload_0
       5: dup
       6: getfield      #2                  // Field count:I
       9: iconst_1
      10: iadd
      11: putfield      #2                  // Field count:I
      14: aload_1
      15: monitorexit
      16: goto          24
      19: astore_2
      20: aload_1
      21: monitorexit
      22: aload_2
      23: athrow
      24: return
    Exception table:
       from    to  target type
           4    16    19   any
          19    22    19   any

同步一段指令集序列通常是由java語(yǔ)言中synchronized語(yǔ)句塊表示的食听,java虛擬機(jī)的指令集有monitorenter(第3行字節(jié)碼)和monitorexit(第15行字節(jié)碼)兩條指令來(lái)支持同步原語(yǔ)。
應(yīng)用到increment()方法時(shí)污茵,進(jìn)入到同步代碼塊之前樱报,首先借助monitorenter指令獲取當(dāng)前unsafe對(duì)象的監(jiān)視器鎖,然后完成count加1的操作泞当,在退出同步代碼塊時(shí)借助monitorexit完成對(duì)監(jiān)視器鎖的釋放迹蛤。

4. 應(yīng)用

對(duì)于方法外部和內(nèi)部的同步操作,底層的實(shí)現(xiàn)原理雖然是不同的襟士,但是表現(xiàn)的結(jié)果是一致的盗飒。但是在實(shí)際應(yīng)用的時(shí)候還是要注意區(qū)分對(duì)象鎖類對(duì)象鎖的區(qū)別。

1. 對(duì)象鎖

1.1 this對(duì)象

public class Unsafe {

   private  int count = 0;

    public void increment() {
      // 借助的是當(dāng)前unsafe對(duì)象的監(jiān)視器鎖
        synchronized (this) {
            count++;
        }
    }

    public static void main(String[] args) {
        Unsafe unsafe = new Unsafe();
        unsafe.increment();
    }
}

這里increment()方法借助的是new出來(lái)的unsafe對(duì)象相關(guān)的對(duì)象鎖來(lái)實(shí)現(xiàn)的線程安全陋桂。

1.2 任意Object對(duì)象

public class Unsafe {

    private int count = 0;
    
    private Object lock = new Object();

    public void increment() {
        // 這里借助object對(duì)象的監(jiān)視器鎖
        synchronized (lock) {
            count++;
        }
    }

    public static void main(String[] args) {
        Unsafe unsafe = new Unsafe();
        unsafe.increment();
    }
}

監(jiān)視器鎖是所有Object對(duì)象相關(guān)的逆趣,所以任意一個(gè)對(duì)象都可以使用如上的寫(xiě)法。這里借助的是new出來(lái)的object對(duì)象相關(guān)的監(jiān)視器鎖來(lái)實(shí)現(xiàn)的線程安全嗜历。針對(duì)同一類new出來(lái)的不同的對(duì)象宣渗,所以它們之間是沒(méi)鎖競(jìng)爭(zhēng)關(guān)系的。

2. 類對(duì)象鎖

2.1 類

public class Unsafe {

    private int count = 0;

    public void increment() {
        synchronized (Unsafe.class) {
            count++;
        }
    }

    public static void main(String[] args) {
        Unsafe unsafe = new Unsafe();
        unsafe.increment();
    }
}

2.2 靜態(tài)方法

public class Unsafe {

    private static int count = 0;

    public static synchronized void increment() {
        count++;
    }

    public static void main(String[] args) {
        Unsafe unsafe = new Unsafe();
        unsafe.increment();
    }
}

因?yàn)閷?duì)象的類的信息是在jvm中的方法區(qū)(永久代)保存的梨州,是被所有的線程共享的痕囱,靜態(tài)方法和成員變量也是被共享的。所以即便將上面的Unsafenew出不同的對(duì)象暴匠,它們之間還是存在鎖競(jìng)爭(zhēng)關(guān)系的鞍恢。

寫(xiě)在最后:最近一段時(shí)間都在為年初跳槽做技術(shù)上的準(zhǔn)備。之前一直對(duì)原理性的東西似懂非懂每窖,希望能通過(guò)寫(xiě)博客的過(guò)程細(xì)化自己對(duì)技術(shù)細(xì)節(jié)的了解帮掉,也希望博客的內(nèi)容能給廣大的java道友提供一些的幫助和提升。由于筆者水平有限窒典,如果內(nèi)容有誤蟆炊,希望大家批評(píng)指出。

參考文獻(xiàn)
. 《深入理解Java虛擬機(jī)》 周志明 著

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末崇败,一起剝皮案震驚了整個(gè)濱河市盅称,隨后出現(xiàn)的幾起案子肩祥,更是在濱河造成了極大的恐慌后室,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件混狠,死亡現(xiàn)場(chǎng)離奇詭異岸霹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)将饺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)贡避,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)痛黎,“玉大人,你說(shuō)我怎么就攤上這事刮吧『ィ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵杀捻,是天一觀的道長(zhǎng)井厌。 經(jīng)常有香客問(wèn)我,道長(zhǎng)致讥,這世上最難降的妖魔是什么仅仆? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮垢袱,結(jié)果婚禮上墓拜,老公的妹妹穿的比我還像新娘。我一直安慰自己请契,他們只是感情好咳榜,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著爽锥,像睡著了一般贿衍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上救恨,一...
    開(kāi)封第一講書(shū)人閱讀 50,096評(píng)論 1 291
  • 那天贸辈,我揣著相機(jī)與錄音,去河邊找鬼肠槽。 笑死擎淤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的秸仙。 我是一名探鬼主播嘴拢,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼寂纪!你這毒婦竟也來(lái)了席吴?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤捞蛋,失蹤者是張志新(化名)和其女友劉穎孝冒,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體拟杉,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡庄涡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了搬设。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片穴店。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡撕捍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泣洞,到底是詐尸還是另有隱情忧风,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布球凰,位于F島的核電站阀蒂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏弟蚀。R本人自食惡果不足惜蚤霞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望义钉。 院中可真熱鬧昧绣,春花似錦、人聲如沸捶闸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)删壮。三九已至贪绘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間央碟,已是汗流浹背税灌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亿虽,地道東北人菱涤。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像洛勉,于是被迫代替她去往敵國(guó)和親粘秆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

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

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法收毫,類相關(guān)的語(yǔ)法攻走,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法此再,異常的語(yǔ)法昔搂,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,603評(píng)論 18 399
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司引润,掛了不少巩趁,但最終還是拿到小米、百度淳附、阿里藐翎、京東阔拳、新浪、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,218評(píng)論 11 349
  • 本文出自 Eddy Wiki 几迄,轉(zhuǎn)載請(qǐng)注明出處:http://eddy.wiki/interview-java.h...
    eddy_wiki閱讀 2,078評(píng)論 0 14
  • 沒(méi)想到,那天真的開(kāi)了一輛沒(méi)有備胎的車(chē)子蔓榄!原來(lái)寶馬X1居然沒(méi)有設(shè)置備胎详恼!真是豈有此理!路上胎爆了坤溃,這下體會(huì)了沒(méi)有備胎...
    陳嘉玲閱讀 254評(píng)論 0 0
  • 通過(guò)a的href=“javascript:window.open('http://www.baidu.com');...
    臭臭臭魁閱讀 3,042評(píng)論 0 0