<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          synchronized 原理知多少

          共 12140字,需瀏覽 25分鐘

           ·

          2021-07-04 04:51

          ????關(guān)注后回復(fù) “進群” ,拉你進程序員交流群????
          作者丨ytao
          來源丨ytao

          synchronized是 Java 編程中的一個重要的關(guān)鍵字,也是多線程編程中不可或缺的一員。本文就對它的使用和鎖的一些重要概念進行分析。

          使用及原理

          synchronized 是一個重量級鎖,它主要實現(xiàn)同步操作,在 Java 對象鎖中有三種使用方式:

          • 普通方法中使用,鎖是當前實例對象。
          • 靜態(tài)方法中使用,當前類的對象。
          • 代碼塊中使用,鎖是代碼代碼塊中配置的對象。

          使用

          在代碼中使用方法分別如下:

          普通方法使用:

          /**
           * 公眾號:ytao
           * 博客:https://ytao.top
           */

          public class SynchronizedMethodDemo{
              public synchronized void demo(){
                  // ......
              }
          }

          靜態(tài)方法使用:

          /**
           * 公眾號:ytao
           * 博客:https://ytao.top
           */

          public class SynchronizedMethodDemo{
              public synchronized static void staticDemo(){
                  // ......
              }
          }

          代碼塊中使用:

          /**
           * 公眾號:ytao
           * 博客:https://ytao.top
           */

          public class SynchronizedDemo{
              public void demo(){
                  synchronized (SynchronizedDemo.class){
                      // ......
                  }
              }
          }

          實現(xiàn)原理

          方法和代碼塊的實現(xiàn)原理使用不同方式:

          代碼塊

          每個對象都擁有一個monitor對象,代碼塊的{}中會插入monitorentermonitorexit指令。當執(zhí)行monitorenter指令時,會進入monitor對象獲取鎖,當執(zhí)行monitorexit命令時,會退出monitor對象釋放鎖。同一時刻,只能有一個線程進入在monitorenter中。

          先將SynchronizedDemo.java使用javac SynchronizedDemo.java命令將其編譯成SynchronizedDemo.class。然后使用javap -c SynchronizedDemo.class反編譯字節(jié)碼。

          Compiled from "SynchronizedDemo.java"
          public class SynchronizedDemo {
            public SynchronizedDemo();
              Code:
                 0: aload_0
                 1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                 4return

            public void demo();
              Code:
                 0: ldc           #2                  // class SynchronizedDemo
                 2: dup
                 3: astore_1
                 4: monitorenter  // 進入 monitor
                 5: aload_1
                 6: monitorexit  // 退出 monitor
                 7: goto          15
                10: astore_2
                11: aload_1
                12: monitorexit  // 退出 monitor
                13: aload_2
                14: athrow
                15return
              Exception table:
                 from    to  target type
                     5     7    10   any
                    10    13    10   any
          }

          上面反編碼后的代碼,有兩個monitorexit指令,一個插入在異常位置,一個插入在方法結(jié)束位置。

          方法

          方法中的synchronized與代碼塊中實現(xiàn)的方式不同,方法中會添加一個叫ACC_SYNCHRONIZED的標志,當調(diào)用方法時,首先會檢查是否有ACC_SYNCHRONIZED標志,如果存在,則獲取monitor對象,調(diào)用monitorentermonitorexit指令。

          通過javap -v -c SynchronizedMethodDemo.class命令反編譯SynchronizedMethodDemo類。-v參數(shù)即-verbose,表示輸出反編譯的附加信息。下面以反編譯普通方法為例。

          Classfile /E:/SynchronizedMethodDemo.class
            Last modified 2020-6-28; size 381 bytes
            MD5 checksum 55ca2bbd9b6939bbd515c3ad9e59d10c
            Compiled from "SynchronizedMethodDemo.java"
          public class SynchronizedMethodDemo
            minor version: 0
            major version: 52
            flagsACC_PUBLICACC_SUPER
          Constant pool:
             #1 
          = Methodref          #5.#13         // java/lang/Object."<init>":()V
             #2 = Fieldref           #14.#15        // java/lang/System.out:Ljava/io/PrintStream;
             #3 = Methodref          #16.#17        // java/io/PrintStream.println:()V
             #4 = Class              #18            // SynchronizedMethodDemo
             #5 = Class              #19            // java/lang/Object
             #6 = Utf8               <init>
             #7 = Utf8               ()V
             #8 = Utf8               Code
             #9 = Utf8               LineNumberTable
            #10 = Utf8               demo
            #11 = Utf8               SourceFile
            #12 = Utf8               SynchronizedMethodDemo.java
            #13 = NameAndType        #6:#7          // "<init>":()V
            #14 = Class              #20            // java/lang/System
            #15 = NameAndType        #21:#22        // out:Ljava/io/PrintStream;
            #16 = Class              #23            // java/io/PrintStream
            #17 = NameAndType        #24:#7         // println:()V
            #18 = Utf8               SynchronizedMethodDemo
            #19 = Utf8               java/lang/Object
            #20 = Utf8               java/lang/System
            #21 = Utf8               out
            #22 = Utf8               Ljava/io/PrintStream;
            #23 = Utf8               java/io/PrintStream
            #24 = Utf8               println
          {
            public SynchronizedMethodDemo();
              descriptor: ()V
              flags: ACC_PUBLIC
              Code:
                stack=1, locals=1, args_size=1
                   0: aload_0
                   1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                   4return
                LineNumberTable:
                  line 50

            public synchronized void demo();
              descriptor: ()V
              flags: ACC_PUBLIC, ACC_SYNCHRONIZED     // ACC_SYNCHRONIZED 標志
              Code:
                stack=1, locals=1, args_size=1
                   0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
                   3: invokevirtual #3                  // Method java/io/PrintStream.println:()V
                   6return
                LineNumberTable:
                  line 80
                  line 106
          }
          SourceFile: "SynchronizedMethodDemo.java"

          上面對代碼塊和方法的實現(xiàn)方式進行探究:

          • 代碼塊通過在編譯后的代碼中添加monitorentermonitorexit指令。
          • 方法中通過添加ACC_SYNCHRONIZED標志,來決定是否調(diào)用monitor對象。

          Java 對象頭

          synchronized鎖的相關(guān)數(shù)據(jù)存放在 Java 對象頭中。Java 對象頭指的 HotSpot 虛擬機的對象頭,使用2個字寬或3個字寬存儲對象頭。

          • 第一部分存儲運行時的數(shù)據(jù),hashCode、鎖標記位、是否偏向鎖、GC分代年齡等等信息,稱作為Mark Word
          • 第二部分存儲對象類型數(shù)據(jù)的指針。
          • 第三部分,如果對象是數(shù)組的話,則用這部分來存儲數(shù)組長度。

          Java 對象頭 Mark Word 存儲內(nèi)容:

          存儲內(nèi)容標志位狀態(tài)
          對象的hashCode、GC分代年齡01無鎖
          指向棧中鎖記錄的指針00輕量級鎖
          指向重量級鎖的指針10重量級鎖
          11GC標記
          線程ID、Epoch(一個時間戳)、GC分代年齡01偏向鎖

          鎖升級

          synchronized 稱為重量級鎖,但 Java SE 1.6 為優(yōu)化該鎖的性能而減少獲取和釋放鎖的性能消耗,引入偏向鎖輕量級鎖

          鎖的高低級別為:無鎖偏向鎖輕量級鎖重量級鎖

          其中鎖的升級是不可逆的,只能由低往高級別升,不能由高往低降。

          偏向鎖

          偏向鎖是優(yōu)化在無多線程競爭情況下,提高程序的的運行性能而使用到的鎖。在Mark Word中存儲一個值,用來標志是否為偏向鎖,在 32 位虛擬機和 64 位虛擬機中都是使用一個字節(jié)存儲,0 為非偏向鎖,1 為是偏向鎖。

          當?shù)谝淮伪痪€程獲取偏向鎖時,會將Mark Word中的偏向鎖標志設(shè)置為 1,同時使用 CAS 操作來記錄這個線程的ID。獲取到偏向鎖的線程,再次進入獲取鎖時,只需判斷Mark Word是否存儲著當前線程ID,如果是,則不需再次進行獲取鎖操作,而是直接持有該鎖。

          撤銷鎖

          如果有其他線程出現(xiàn),嘗試獲取偏向鎖,讓偏向鎖處于競爭狀態(tài),那么當前偏向鎖就會撤銷。撤銷偏向鎖時,首先會暫停持有偏向鎖的線程,并將線程ID設(shè)為空,然后檢查該線程是否存活:

          • 當暫停線程非存活,則設(shè)置對象頭為無鎖狀態(tài)。
          • 當暫停線程存活,執(zhí)行偏向鎖的棧,最后對象頭的保存其他獲取到偏向鎖的線程ID或者轉(zhuǎn)向無鎖狀態(tài)。

          當確定代碼一定執(zhí)行在多線程訪問中時,那么這時的偏向鎖是無法發(fā)揮到優(yōu)勢,如果繼續(xù)使用偏向鎖就顯得過于累贅,給系統(tǒng)帶來不必要的性能開銷,此時可以設(shè)置 JVM 參數(shù)-XX:BiasedLocking=false來關(guān)閉偏向鎖。

          輕量級鎖

          代碼進入同步塊的時候,如果對象頭不是鎖定狀態(tài),JVM 則會在當前線程的棧楨中創(chuàng)建一個鎖記錄的空間,將鎖對象頭的Mark Word復(fù)制一份到鎖記錄中,這份復(fù)制過來的Mark Word叫做Displaced Mark Word。然后使用 CAS 操作將鎖對象頭中的Mark Word更新為指向鎖記錄的指針。如果更新成功,當前線程則會獲得鎖,如果失敗,JVM 先檢查鎖對象的Mark Word是否指向當前線程,是指向當前線程的話,則當前線程已持有鎖,否則存在多線程競爭,當前線程會通過自旋獲取鎖,這里的自旋可以理解為循環(huán)嘗試獲取鎖,所以這過程是消耗 CPU 的過程。當輕量級鎖存在競爭狀態(tài)并自旋獲取輕量級鎖失敗時,輕量級鎖就會膨脹為重量級鎖,鎖對象的Mark Word會更新為指向重量級鎖的指針,等待獲取鎖的線程進入阻塞狀態(tài)。

          解鎖

          輕量級鎖解鎖是使用 CAS 操作將鎖記錄替換到Mark Word中,如果替換成功,則表示同步操作已完成。如果失敗,則表示其他競爭線程嘗試過獲取該輕量級鎖,需要在釋放鎖的同時,去喚醒其他被阻塞的線程,被喚醒的線程回去再次去競爭鎖。

          總結(jié)

          通過分析synchronized的使用以及 Java SE 1.6 升級優(yōu)化鎖后的設(shè)計,可以看出其主要是解決是通過多加入兩級相對更輕巧的偏向鎖和輕量級鎖來優(yōu)化重量級鎖的性能消耗,但是這并不是一定會起到優(yōu)化作用,主要是解決大多數(shù)情況下不存在多線程競爭以及同一線程多次獲取鎖的的優(yōu)化,這也是根據(jù)平時在編碼中多觀察多反思得出的權(quán)衡方案。

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點這里好文分享給更多人↓↓

          瀏覽 31
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一区二区三区久久久 | 日本免费亚洲 | 黄色片成年人免费的 | 欧美性生交大片免费看A片免费 | 奇米伊人精品在线 |