【并發編程】2-Synchronized

基本概念

  1. 線程安全問題三個要素:多線程、共享資源、非原子性操作;
  2. 產生的根本原因:多條線程同時對一個共享資源進行非原子性操作;
  3. Synchronized解決線程安全問題的方式:通過互斥鎖將多線程的并行執行變為單線程串行執行,同一時刻只讓一條線程執行,也就是當多條線程同時執行一段被互斥鎖保護的代碼(臨界資源)時,需要先獲取鎖,這時只會有一個線程獲取到鎖資源成功執行,其他線程將陷入等待的狀態,直到當前線程執行完畢釋放鎖資源之后,其他線程才能執行;
  4. Synchronized可以保證可見性和有序性,但無法禁止指令重排序

Synchronized鎖粒度及應用方式

Synchronized鎖的三種粒度

  1. 鎖粒度synchronized本質上是通過對象來加鎖的,根據不同的對象類型可分為不同的鎖粒度;
    1. this:當前實例鎖;
    2. object:對象實例鎖;
    3. class:對象鎖;

應用方式

  1. 修飾實例成員方法:使用的是this鎖類型,這個this代表的是當前new出來的對象;

    synchronized void method() {//業務代碼
    }
    
  2. 修飾靜態成員方法:使用的是this鎖類型,但由于靜態成員屬于類對象,所以這個this代表的是class對象;

    synchronized static void method() {//業務代碼
    }
    
  3. 修飾代碼塊:修飾代碼塊時,可以指定鎖對象,可以將任意class類對象做為鎖資源;

    synchronized(SyncIncrDemo.class) {//業務代碼
    }
    
    1. 使用String對象加鎖:因為修飾代碼塊時,可以將任意class類對象做為鎖資源,而JAVA中字符串是以Sting對象存儲使用的,并且在JVM里有個字符串常量池,用于存儲字符串,那么相同值的字符串變量的引用地址可以是同一個,基于這個特性我們可以將synchronized鎖粒度變細;

      如:將每一個訂單的ID轉化為字符串然后進行加鎖,這樣就能降低鎖粒度提高系統并發,但在加鎖時需要考慮加鎖的String對象是不是同一個,需要考慮String對象使用的是堆中對象還是字符串常量池中的對象,若是堆中對象需要使用.intern()將堆中對象刷入字符串常量池中;

      /*** @author xrl* @date 2024/6/25 22:47*/
      public class Demo1 {public static void main(String[] args) {new Thread(() ->t1(111L), "AAA").start();new Thread(() ->t2(111L), "BBB").start();new Thread(() -> t2(111L), "CCC").start();}public static void t1(Long orderId){// 不能使用  String lock = orderId + "; 原因是orderId是入參,而方法可能在多個地方調用// 所以編譯器不會使用常量折疊技術對其進行優化,編譯后的代碼會被轉化為new StringBuilder().append(orderId).append("").toString()String lock = new String(orderId + "").intern();synchronized (lock){System.out.println(Thread.currentThread().getName() + "拿到鎖");try {System.out.println("t1業務執行");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "釋放鎖");}}public static void t2(Long orderId){String lock = new String(orderId + "").intern();synchronized (lock){System.out.println(Thread.currentThread().getName() + "拿到鎖");try {System.out.println("t2業務執行");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "釋放鎖");}}
      }
      

實現原理

  1. Synchronized是基于Monitor(管程)對象實現的
    1. 獲取鎖:進入管程對象(顯式型:monitorenter指令);
    2. 釋放鎖:退出管程對象(顯示型:monitorexit指令);
  2. synchronized修飾方法使用時是隱式同步的,是通過調用指令,讀取運行時常量池中的方法ACC_SYNCHRONIZED標識實現的,也就無法通過javap反編譯看到進入/退出管程對象的指令;

JAVA對象的內存布局

  1. 分為三個區域對象頭、實例數據、對齊填充

    在這里插入圖片描述

    1. 對象頭:存儲MarkWord和類型指針(ClassMetadataAddress/KlassWord);如果為數組對象,還會存數組長度(ArrayLength

      • 主要包含unused未使用的空間HashCodeage分代年齡biased_lock是否偏向鎖lock鎖標記位ThreadID持有偏向鎖的線程IDepoch偏向鎖時間戳ptr_to_lock_record指向線程本地棧中lock_record的指針ptr_to_heavyweight_monitor指向堆中monitor對象的指針

      • 64位系統中MarkWord結構

        在這里插入圖片描述

    2. 實例數據:存放當前對象的屬性成員信息,以及父類屬性成員信息;

    3. 對齊填充:虛擬機要求對象起始地址必須是8byte的整數倍,避免減少堆內存的碎片空間,并且方便操作系統讀取;

monitor對象

  1. 概念

    1. monitor本質是一個特殊的對象,存在于堆中,并且是線程私有的
    2. 每個java對象都存在一個monitor對象與之關聯,當一個monitor被某個線程持有后,便會處于鎖定狀態;由ObjectMonitor實現,具體代碼位于HotSpot源碼的ObjectMonitor.hpp文件中;
  2. 與線程之間的關聯:每個線程都有一個可用的monitor record列表,同時也存在一個全局的可用列表,每一個鎖住的對象,都會和monitor關聯(對象頭的MarkWord中的ptr_to_heavyweight_monitor,指向monitor的起始地址),同時monitor中有一個Owner字段,存放擁有該鎖的線程唯一標識,表示該鎖被這個線程占用;

  3. 內部結構

    在這里插入圖片描述

    1. Contention List:競爭隊列,**存放所有請求鎖的線程(**后續1.8版本中的_cxq);
    2. Entry List:一個雙向鏈表,存放Contention List有資格成為候選資源的線程
    3. Wait Set:哈希表,調用Object.wait()方法后,被阻塞的線程被放置在這里
    4. OnDeck:任意時刻,最多只有一個線程正在競爭鎖資源,該線程被稱為OnDeck
    5. Owner擁有這個monitor record線程的唯一標識,為NULL說明沒有被占用;
    6. !Owner:當前釋放鎖的線程;
    7. RcThis:表示blocked阻塞或waiting等待在該monitor record上的線程個數;
    8. Nest:用來實現重入鎖的計數。
    9. Candidate:用來避免不必要的阻塞或等待線程喚醒,因為每一次只有一個線程能夠成功擁有鎖,如果每次前一個釋放鎖的線程,喚醒所有正在阻塞或等待的線程,會引起不必要的上下文切換(從阻塞到就緒,然后因為競爭鎖失敗又被阻塞),從而導致性能嚴重下降。Candidate只有兩種可能的值,0表示沒有需要喚醒的線程;1表示要喚醒一個繼任線程來競爭鎖;
    10. HashCode:保存從對象頭拷貝過來的HashCode值(可能還包含GC age)。
    11. EntryQ:關聯一個系統互斥鎖(semaphore),阻塞所有試圖鎖住monitor record失敗的線程;
  4. 使用流程

    1. 每個獲取鎖或等待鎖的線程都會被封裝成ObjectWaiter對象;
    2. Monitor有兩個隊列_WaitSet_EntryList,用來保存ObjectWaiter對象列表;
    3. 當多個線程同時訪問一段同步代碼時,獲取到鎖的對象會進入到_owner區域,并將owner變量設置為當前線程的唯一標識,同時nest計數器加1,沒有獲取到鎖的對象會加入_EntryList隊列中等待;
    4. 若線程調用Object.wait()方法,會釋放當前持有的monitorowenr變量回復為null,同時nest計數器減1,并將線程放入到waitSet集合中等待被喚醒;
    5. 當調用Monitor對象的notify()notifyAll() 方法來喚醒 WaitSet 中的等待線程時,會將等待線程移動到 EntryList 隊列中等待獲取鎖的機會;

修飾代碼塊的原理

  1. 反編譯代碼

    1. 源代碼

      public class SyncDemo{int i;public void incr(){synchronized(this){i++;}}
      }
      
    2. 字節碼javac SyncDemo.java javap -p -v -c SyncDemo.class

      Classfile /Users/xrl/nacos/SyncDemo.classLast modified 2024-5-16; size 388 bytesMD5 checksum 0c87d23a96c5f67c7d97908dbd338f95Compiled from "SyncDemo.java"
      public class SyncDemominor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
      Constant pool:#1 = Methodref          #4.#18         // java/lang/Object."<init>":()V#2 = Fieldref           #3.#19         // SyncDemo.i:I#3 = Class              #20            // SyncDemo#4 = Class              #21            // java/lang/Object#5 = Utf8               i#6 = Utf8               I#7 = Utf8               <init>#8 = Utf8               ()V#9 = Utf8               Code#10 = Utf8               LineNumberTable#11 = Utf8               incr#12 = Utf8               StackMapTable#13 = Class              #20            // SyncDemo#14 = Class              #21            // java/lang/Object#15 = Class              #22            // java/lang/Throwable#16 = Utf8               SourceFile#17 = Utf8               SyncDemo.java#18 = NameAndType        #7:#8          // "<init>":()V#19 = NameAndType        #5:#6          // i:I#20 = Utf8               SyncDemo#21 = Utf8               java/lang/Object#22 = Utf8               java/lang/Throwable
      {int i;descriptor: Iflags:public SyncDemo();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 1: 0/*-------synchronized修飾incr()中代碼塊,反匯編之后得到的字節碼文件--------*/public void incr();descriptor: ()Vflags: ACC_PUBLICCode:stack=3, locals=3, args_size=10: aload_01: dup2: astore_13: monitorenter // monitorenter進入同步4: aload_05: dup6: getfield      #2                  // Field i:I9: iconst_110: iadd11: putfield      #2                  // Field i:I14: aload_115: monitorexit // monitorexit退出同步16: goto          2419: astore_220: aload_121: monitorexit // monitorexit退出同步22: aload_223: athrow24: returnException table:from    to  target type4    16    19   any19    22    19   anyLineNumberTable:line 5: 0line 6: 4line 7: 14line 8: 24StackMapTable: number_of_entries = 2frame_type = 255 /* full_frame */offset_delta = 19locals = [ class SyncDemo, class java/lang/Object ]stack = [ class java/lang/Throwable ]frame_type = 250 /* chop */offset_delta = 4
      }
  2. 從字節碼中可知,synchronized修飾代碼塊,是基于進入管程monitorenter和退出管程monitorexit指令實現的,其中monitorenter指令指向同步代碼塊的開始位置,monitorexit指令則指明同步代碼塊的結束位置;
    有兩條monitorexit指令的原因是解決方法異常結束時,鎖釋放問題;

修飾方法的原理

  1. 反編譯代碼

    1. 源代碼

      public class SyncDemo {int i;public synchronized void incr() {i++;}
      }
      
    2. 字節碼

      Classfile /Users/xrl/nacos/SyncDemo.classLast modified 2024-5-16; size 388 bytesMD5 checksum 0c87d23a96c5f67c7d97908dbd338f95Compiled from "SyncDemo.java"
      public class SyncDemominor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
      Constant pool:#1 = Methodref          #4.#18         // java/lang/Object."<init>":()V#2 = Fieldref           #3.#19         // SyncDemo.i:I#3 = Class              #20            // SyncDemo#4 = Class              #21            // java/lang/Object#5 = Utf8               i#6 = Utf8               I#7 = Utf8               <init>#8 = Utf8               ()V#9 = Utf8               Code#10 = Utf8               LineNumberTable#11 = Utf8               incr#12 = Utf8               StackMapTable#13 = Class              #20            // SyncDemo#14 = Class              #21            // java/lang/Object#15 = Class              #22            // java/lang/Throwable#16 = Utf8               SourceFile#17 = Utf8               SyncDemo.java#18 = NameAndType        #7:#8          // "<init>":()V#19 = NameAndType        #5:#6          // i:I#20 = Utf8               SyncDemo#21 = Utf8               java/lang/Object#22 = Utf8               java/lang/Throwable
      {int i;descriptor: Iflags:public SyncDemo();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 1: 0public void incr();descriptor: ()Vflags: ACC_PUBLICCode:stack=3, locals=3, args_size=10: aload_01: dup2: astore_13: monitorenter4: aload_05: dup6: getfield      #2                  // Field i:I9: iconst_110: iadd11: putfield      #2                  // Field i:I14: aload_115: monitorexit16: goto          2419: astore_220: aload_121: monitorexit22: aload_223: athrow24: returnException table:from    to  target type4    16    19   any19    22    19   anyLineNumberTable:line 5: 0line 6: 4line 7: 14line 8: 24StackMapTable: number_of_entries = 2frame_type = 255 /* full_frame */offset_delta = 19locals = [ class SyncDemo, class java/lang/Object ]stack = [ class java/lang/Throwable ]frame_type = 250 /* chop */offset_delta = 4
      }
      SourceFile: "SyncDemo.java"
      xrl@xrldeMacBook-Air nacos % open/
      The file /Users/xrl/nacos/。 does not exist.
      xrl@xrldeMacBook-Air nacos % open ./ 
      xrl@xrldeMacBook-Air nacos % javac SyncDemo.java         
      xrl@xrldeMacBook-Air nacos % javap -p -v -c SyncDemo.class
      Classfile /Users/xrl/nacos/SyncDemo.classLast modified 2024-5-16; size 276 bytesMD5 checksum ef45d44f86eef93281fefeda549e1283Compiled from "SyncDemo.java"
      public class SyncDemominor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
      Constant pool:#1 = Methodref          #4.#14         // java/lang/Object."<init>":()V#2 = Fieldref           #3.#15         // SyncDemo.i:I#3 = Class              #16            // SyncDemo#4 = Class              #17            // java/lang/Object#5 = Utf8               i#6 = Utf8               I#7 = Utf8               <init>#8 = Utf8               ()V#9 = Utf8               Code#10 = Utf8               LineNumberTable#11 = Utf8               incr#12 = Utf8               SourceFile#13 = Utf8               SyncDemo.java#14 = NameAndType        #7:#8          // "<init>":()V#15 = NameAndType        #5:#6          // i:I#16 = Utf8               SyncDemo#17 = Utf8               java/lang/Object
      {int i;descriptor: Iflags:public SyncDemo();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 1: 0public synchronized void incr();descriptor: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZEDCode:stack=3, locals=1, args_size=10: aload_01: dup2: getfield      #2                  // Field i:I5: iconst_16: iadd7: putfield      #2                  // Field i:I10: returnLineNumberTable:line 5: 0line 6: 10
      }
  2. 由字節碼可知synchronized修飾的方法,并沒有出現monitorenter指令和monitorexit指令,取得代之的是:flags: ACC_PUBLIC之后增加了一個ACC_SYNCHRONIZED標識。這個標識指明了當前方法是一個同步方法,JVM通過這個ACC_SYNCHRONIZED訪問標志,來辨別一個方法是否為同步方法,從而執行相應的同步調用;

JVM對synchronized的優化

鎖狀態

JDK1.6之后,synchronized鎖的狀態總共有四種:無鎖狀態、偏向鎖、輕量級鎖和重量級鎖;隨著線程的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖,但是鎖的升級一般是單向的,也就是說只能從低到高升級,通常不會出現鎖的降級(只針對用戶線程,在STW可能會發生鎖降級)。

無鎖狀

  1. new一個對象時,會默認啟動匿名偏向鎖,但為了避免JVM在啟動階段大量創建對象,從而導致偏向鎖競爭過多影響性能,則在JVM啟動階段會次用延遲偏向鎖策略,也就是等待一定時間后再開啟偏向鎖,默認為4,可通過-XX:BiasedLockingStartupDelay = xx設置;
  2. 對于一個新創建的對象,由于在沒有成為真正偏向鎖之前,對象頭markword中的線程ID會一直為空,這種被稱為概念上的無鎖對象,但markword的鎖標識為101

偏向鎖

  1. 為了減少同一線程獲取鎖的代價,如CAS操作帶來的耗時等;
  2. 核心思想如果一個線程獲得了鎖,那么鎖就進入偏向模式,此時Mark Word的結構也變為偏向鎖結構,當這個線程再次請求鎖時,無需再做任何同步操作;
  3. 操作流程
    1. Load-and-test,就是簡單判斷一下當前線程id是否與Markword中的線程id是否一致;
    2. 如果一致,則說明此線程持有的偏向鎖,沒有被其他線程覆蓋,直接執行后續代碼;
    3. 如果不一致,則要檢查一下對象是否還屬于可偏向狀態,即檢查“是否偏向鎖”標志位;
    4. 如果還未偏向,則利用CAS操作來競爭鎖,再次將ID放進去,即重復第一次獲取鎖的動作;
  4. 偏向鎖主要是在鎖競爭不激烈的場合對性能的提升,但是對于鎖競爭比較激烈的場合,偏向鎖作用就很小了,甚至成為累贅,可以通過XX:-UseBiasedLocking命令關閉;
撤銷過程
  1. 在一個安全點停止擁有鎖的線程;
  2. 遍歷線程棧,如果存在鎖記錄的話,需要修復鎖記錄和Markword,使其變成無鎖狀;
  3. 喚醒當前線程,將當前鎖升級成輕量級鎖;
膨脹過程
  1. 當首個線程進程嘗試獲取鎖時,會通過CAS操作,將自己的threadID設置到MarkWord中,如果設置成功,則證明拿到偏向鎖
  2. 當線程再次嘗試獲取鎖時,發現自己的線程ID和對象頭中的偏向線程ID一致,則在當前線程棧的lock record鎖記錄中添加一個空的Displaced Mark Word(表示的是原先持有偏向鎖的線程ID和相關信息被移動到哪里的標記),并不需要CAS操作;
  3. 重新偏向,當其他線程進入同步塊時,發現偏向線程不是自己,則進入偏向鎖撤銷的邏輯;當達到全局安全點時,如果發現偏向線程掛了,那就把偏向鎖撤銷,并將對象頭內的MarkWord修復為無鎖狀態,自己嘗試獲取偏向鎖;
  4. 可如果原本的偏向線程還存活,重新偏向失敗后,鎖開始膨脹為輕量級鎖,原來的線程仍然持有鎖

輕量級鎖

鎖膨脹過程
  1. 根據markWork判斷是否有線程持有鎖,如果有則在當前線程棧中創建一個lock record復制markWord,并通過CAS將當前線程棧的lock record地址放入對象頭中,如果成功則說明獲取到輕量級鎖
  2. 如果失敗則說明鎖已經被其他線程持有了,此時記錄線程的重入次數(把ock recordmarkword設置為null),并進入自適應自旋
  3. 如果自旋到一定的次數后還未獲取到鎖,則說明目前競爭較重,則膨脹為重量級鎖
自旋
  1. 自旋鎖:是指當一個線程在獲取鎖的時候,如果鎖已經被其它線程獲取,那么該線程將循環等待,然后不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖才會退出循環;
    1. 存在的問題
      1. 若加鎖代碼執行慢或鎖競爭激烈,則需要等待較長時間,會導致CPU空轉,消耗大量CPU資源;
      2. 非公平鎖,若存在多個線程自旋等待同一把鎖,可能對導致有些線程一致獲取不到鎖;
    2. 解決辦法:通過-XX:PreBliockSpin給線程空循環設置一個次數,默認是10次,或者自旋線程數超過CPU核數的一半時,鎖會再次膨脹升級為重量級鎖;
  2. 自適應自旋鎖:線程空循環次數不固定,而是會根據實際情況動態調整;
    1. 在大部分情況下,已經獲取到鎖的線程再次嘗試獲取鎖,大概率能成功拿到該鎖,所以虛擬機會延長這個線程的自旋次數;

重量級鎖

  1. 當出現較大競爭鎖膨脹為重量級鎖時,對象頭的markword指向堆中的monitor,此時線程會封裝為一個ObjectWaiter對象,并插入到monitor_cxq競爭隊列中然后掛起線程,等待鎖釋放;
  2. 當持有鎖的線程釋放后,會將_cxq競爭隊列中的ObjectWaiter對象,移到EntryList中,并隨機挑選一個對象也就是一個線程喚醒,被選中的線程稱之為Heir Presumptive假定繼承人,之后Heir Presumptive會去嘗試獲取鎖,但在這段期間其他線程頁可能嘗試獲取鎖,所以Heir Presumptive不一定可以獲取到鎖,當沒有獲取到鎖后那么它會退回到等待隊列中,成為EntryList中的最后一個對象;
  3. 當線程獲取到鎖后,調用Object.wait()方法后,會將線程加入到WaitSet中,當被Object.notify()喚醒后,會將線程從WaitSet移動到_cxqEntryList中去,并且由于Object.wait()、Object.notify()方法主要依賴于Monitor對象實現的,所以鎖對象調用這兩個方法時的鎖狀態如果為偏向鎖或輕量級鎖,則會先膨脹成重量級鎖;

鎖膨脹過程

  1. 無鎖態JVM啟動后-XX:BiasedLockingStartupDelay(默認四秒)內的普通對象和四秒后的匿名偏向鎖對象;
  2. 偏向鎖:只有一個線程進入臨界區;
    1. 偏向鎖未開啟:直接膨脹為輕量級鎖
      1. MarkWord中鎖標識位信息除外的其他所有信息copy到自己的棧內存的Lock Record中,再嘗試通過CASMarkWord中的ptr_to_lock_record指向自己的棧內Lock Record,替換成功則獲取到鎖,沒有則繼續自旋;
    2. 匿名偏向鎖
      1. MarkWord中鎖標識位信息除外的其他所有信息copy到自己的棧內存的Lock Record中,并嘗試通過CAS將自己的線程ID設置到MarkWord中;這個線程,后續再次使用這個鎖時,無需進行加鎖和鎖釋放操作,只需要在Lock Record添加一個空的MarkWord
      2. 重新偏向:當嘗試獲取鎖時,發現線程ID與MarkWord中記錄的線程ID不相同,則進入偏向鎖撤銷的邏輯;當達到全局安全點時,發現之前持有鎖的線程執行完畢,則會發生偏向鎖撤銷(清除鎖記錄回歸無鎖態),然后線程可以通過CAS將自己重新設置到MarkWord
      3. 重新偏向失敗:鎖膨脹為輕量級鎖,嘗試通過CASMarkWord中的ptr_to_lock_record指向自己的棧內Lock Record,替換成功則獲取到鎖,沒有則繼續自旋;
    3. 調用Object.wait()方法,直接膨脹為重量級鎖
  3. 輕量級鎖多個線程交替進入臨界區
    1. 當出現重度競爭、耗時過長、自旋過多等情況時會膨脹為重量級鎖;
      1. 自旋線程數超過CPU核數的一半;
      2. 自旋超過-XX:PreBliockSpin默認10次;
    2. 調用Object.wait()方法,直接膨脹為重量級鎖;
  4. 重量級鎖:多個線程同時進入臨界區;

鎖狀態的內存布局分析

  1. 引入架包:

    <dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.16</version>
    </dependency>
    
  2. MarkWord結構:

    在這里插入圖片描述

  3. 運行代碼:

    package com.xrl.test;import org.openjdk.jol.info.ClassLayout;/*** @version [v1.0]* @author: [xrl]* @create: [2024/05/21 16:14]**/
    public class ObjectHead {public String str;public static void main(String[] args) throws InterruptedException {/**無鎖態:虛擬機剛啟動時 new 出來的對象處于無鎖狀態**/ObjectHead obj = new ObjectHead();System.out.println(ClassLayout.parseInstance(obj).toPrintable());/**com.xrl.test.ObjectHead object internals:OFF  SZ               TYPE DESCRIPTION               VALUE0   8                    (object header: mark)     0x0000000000000001 (non-biasable; age: 0) // 對象頭 non-biasable (轉化為二進制最后三位表示,鎖標志位狀態:001)8   4                    (object header: class)    0xf800c105                               // ClassPointer指針12   4   java.lang.String ObjectHead.str            null                                    // 成員變量Instance size: 16 bytes                                                                     // 共占用16個字節Space losses: 0 bytes internal + 0 bytes external = 0 bytes total*//**輕量級鎖:對于真正的無鎖態對象obj加鎖之后的對象處于輕量級鎖狀態**/synchronized (obj) {// 查看對象內部信息System.out.println(ClassLayout.parseInstance(obj).toPrintable());/**com.xrl.test.ObjectHead object internals:OFF  SZ               TYPE DESCRIPTION               VALUE0   8                    (object header: mark)     0x0000000309dcd9d8 (thin lock: 0x0000000309dcd9d8) // (轉化為二進制最后三位表示,鎖標志位狀態:000)8   4                    (object header: class)    0xf800c10512   4   java.lang.String ObjectHead.str            nullInstance size: 16 bytesSpace losses: 0 bytes internal + 0 bytes external = 0 bytes total*/}/**匿名偏向鎖:休眠4S后再創建出來的對象處于匿名偏向鎖狀態**/Thread.sleep(4000);ObjectHead obj1 = new ObjectHead();System.out.println(ClassLayout.parseInstance(obj1).toPrintable());/**com.xrl.test.ObjectHead object internals:OFF  SZ               TYPE DESCRIPTION               VALUE0   8                    (object header: mark)     0x0000000000000005 (biasable; age: 0)  //(轉化為二進制最后三位表示,鎖標志位狀態:101)8   4                    (object header: class)    0xf800c10512   4   java.lang.String ObjectHead.str            nullInstance size: 16 bytesSpace losses: 0 bytes internal + 0 bytes external = 0 bytes total*//**重量級鎖:調用wait方法之后鎖對象直接膨脹為重量級鎖狀態**/new Thread(() -> {try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}).start();Thread.sleep(1);synchronized (obj) {// 查看對象內部信息System.out.println(ClassLayout.parseInstance(obj).toPrintable());/*** com.xrl.test.ObjectHead object internals:* OFF  SZ               TYPE DESCRIPTION               VALUE*   0   8                    (object header: mark)     0x00007fe748016c0a (fat lock: 0x00007fe748016c0a) (轉化為二進制最后三位表示,鎖標志位狀態:010)*   8   4                    (object header: class)    0xf800c105*  12   4   java.lang.String ObjectHead.str            null* Instance size: 16 bytes* Space losses: 0 bytes internal + 0 bytes external = 0 bytes total*/}}
    }
    /*** 拋出異常原因:違法的監控狀態異常。當某個線程試圖等待一個自己并不擁有的對象(Obj)的監控器或者通知其他線程等待該對象(Obj)的監控器時,拋出該異常* Exception in thread "Thread-1" java.lang.IllegalMonitorStateException* at java.lang.Object.wait(Native Method)* at java.lang.Object.wait(Object.java:502)* at com.xrl.test.ObjectHead.lambda$main$0(ObjectHead.java:53)* at java.lang.Thread.run(Thread.java:750)*/
    

同步消除

  1. Java虛擬機在編譯代碼時,通過會對運行上下文進行掃描,從而去除不可能存在共享資源競爭的鎖,通過這種方式消除沒有必要的鎖,可以節省毫無意義的獲取鎖開銷;

鎖的可重入

  1. 一個線程獲得一個對象鎖后,運行他再次請求該對象鎖;

  2. synchronized是基于monitor實現的,每次重入monitor中的計數器會加一;

  3. 當子類繼承父類時,子類可以通過可重入鎖,調用父類的同步方法;

    class Parent {public synchronized void parentMethod() {System.out.println("Parent method");}
    }class Child extends Parent {public synchronized void childMethod() {System.out.println("Child method");parentMethod(); // 在子類中調用父類的同步方法}
    }public class Main {public static void main(String[] args) {Child child = new Child();child.childMethod();}
    }
    

其他機制

等待/喚醒機制

  1. wait()、notify()、notifyAll()這三個方法在使用時,必須處在synchronized代碼塊或方法中,否則會拋出IllegalMonitorStateException異常,這是由于這三個方法都依賴于monitor對象實現這也是三個方法處在Object對象中的原因,而synchronized關鍵字決定著一個JAVA對象會不會生成monitor對象;
  2. wait()、sleep()方法的區別:
    1. wait()方法會釋放當前持有的鎖,并將線程移入waitSet中;
    2. sleep()方法只會讓線程休眠并不會釋放鎖(類似于執行for(;;){}死循環);

線程中斷機制

  1. JDK1.2遺棄Thread.stop()后,JAVA就沒有提供強制性停止執行中線程的方法,而是提供了協調式的方式

    //中斷線程(實例方法)
    public void Thread.interrupt();
    //判斷線程是否被中斷(實例方法)
    public boolean Thread.isInterrupted();
    //判斷是否被中斷并清除當前中斷狀態(靜態方法)
    public static boolean Thread.interrupted();
    
  2. 使用:我們可以通過調用Thread.interrupt()來進行線程中斷,但由于線程中斷是協調式的,所以他并不會去停止線程,而是需要我們手動進行中斷檢測并結束線程;并且當線程處于阻塞狀態或者嘗試執行一個阻塞操作時,我們調用線程中斷方法,執行中斷操作后會拋出InterruptedException異常;

        public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(){@Overridepublic void run(){try {// 判斷當前線程是否已中斷,執行后會對中斷狀態進行復位while (!Thread.interrupted()) {System.out.println("1111");// 線程阻塞,拋出異常// TimeUnit.SECONDS.sleep(1);}System.out.println("線程中斷");// 輸出false 說明中斷狀態復位System.out.println(this.isInterrupted());} catch (Exception e) {System.out.println(e);}}};t1.start();TimeUnit.SECONDS.sleep(2);// 中斷線程t1.interrupt();}
    
  3. synchronized與線程中斷:對于synchronized而言,一個線程的執行只有兩種狀態,一種是獲取了鎖正在執行,一種是沒獲取到鎖在阻塞額等待;那么他們即使調用中斷線程的方法,也不會生效;

synchronized為什么不禁止指令重排序

  1. synchronized是通過互拆鎖的方式來保證線程安全的,它的本質是將多線程并行執行變成單線程的串行執行,而指令重排序可能導致的問題是多線程環境程序亂序執行的問題,所以指令重排序對synchronized而言并不會存在亂序問題,反而可以提升串行執行時的性能;

synchronized性能不佳的原因

  1. synchronized是基于進入和退出Monitor管程實現的,而Monitor底層時依賴于操作系統的Mutex Lock,所以在其獲取鎖或者釋放鎖的時候都需要經過操作系統的調用,會涉及到頻繁的用戶態與內核態之間的切換,從而導致性能不佳;
  2. 但在并發競爭不高的情況下,由于synchronized幾種鎖狀態、鎖消除等技術的優化,synchronized性能并不差;

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/36871.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/36871.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/36871.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

昇思25天學習打卡營第4天|數據集Dataset

數據集 Dataset 介紹 之前說過&#xff0c;MindSpore是基于Pipeline&#xff0c;通過Dataset和Transformer進行數據處理。Dataset在其中是用來加載原始數據的。mindSpore提供了數據集加載接口&#xff0c;可以加載文本、圖像、音頻等&#xff0c;同時也可以自定義加載接口。此…

【UE開發】游戲庫存UI系統Demo

1.項目介紹 1.描述&#xff1a;一種用于存儲記錄玩家物品的游戲內可視化操作系統。 2.演示&#xff1a;https://www.bilibili.com/video/BV1f53neVEfW/?vd_source50dea901fd12253f417c48b937975b0d 3.大綱&#xff1a; 4.樣式&#xff1a; 2.W_Inventory_Main_01&#xff08;…

CORE Mobility Errorr的調試

在運行CORE tutorial 3中的mobility示例時&#xff0c;出現如下錯誤&#xff1a; 當看到這個問題的時候&#xff0c;并沒有仔細去分析日志和現象&#xff0c;在core-daemon的進程打印界面只看了一下最后的出錯堆棧&#xff1a; 2024-06-27 10:43:48,614 - ERROR - _server:_ca…

MySQL8 新特性——公用表表達式用法 with t1 as (select * from user)

MySQL8 新特性——公用表表達式用法_mysql ctes-CSDN博客 1.普通公用表表達式 MySQL8 新特性——公用表表達式用法 在MySQL 8.0及更高版本中&#xff0c;引入了公用表表達式&#xff08;Common Table Expressions&#xff0c;CTEs&#xff09;&#xff0c;它是一種方便且可重…

docker部署vue項目

1.下載docker desktop軟件 Docker Desktop啟動的時候&#xff0c;有可能彈框提示"WSL2 installations is incomplete"&#xff0c;這是您的系統中沒有安裝WSL2內核的原因&#xff0c;打開【https://aka.ms/wsl2kernel ,在打開的頁面中有一個Linux內核更新包"鏈…

【python011】經緯度點位可視化html生成(有效方案)

1.熟悉、梳理、總結項目研發實戰中的Python開發日常使用中的問題、知識點等&#xff0c;如獲取省市等邊界區域經緯度進行可視化&#xff0c;從而輔助判斷、決策。 2.歡迎點贊、關注、批評、指正&#xff0c;互三走起來&#xff0c;小手動起來&#xff01; 3.歡迎點贊、關注、批…

Android InputReader 輸入事件處理流程

Android系統輸入事件產生的底層主要是輸入子系統&#xff0c;Android 中的輸入設備有很多&#xff0c;例如屏幕&#xff0c;鼠標&#xff0c;鍵盤等都是輸入設備&#xff0c;對于應用開發者&#xff0c;接觸最多的也就是屏幕了。 1. 當輸入設備可用時&#xff0c;Linux會在 /de…

tensorRT的安裝

在這個網址找到適合自己的版本&#xff0c;尤其是找到合適的cuda版本&#xff1a; https://pypi.nvidia.com/ 然后直接pip 安裝&#xff1a; 比如&#xff1a; pip install https://pypi.nvidia.com/tensorrt-cu11/tensorrt-cu11-10.1.0.tar.gz 也可以&#xff1a; wget http…

【MotionCap】SLAHMR 在 Colab 的demo運行筆記

【MotionCap】SLAHMR slahmr將人類和相機運動與野外視頻分離 CVPR 2023跳至主要內容 SLAHMR (supports 4D Humans).ipynb SLAHMR (supports 4D Humans).ipynb_筆記本已移除星標Google Colab demo for: SLAHMR - Simultaneous Localization And Human Mesh Recovery @inproc…

STM32將外部SDRAM空間作為系統堆(Heap)空間

概述 stm32可以外擴很大的sram&#xff0c;常見外部sram的初始化函數一般是c語言寫的&#xff0c;默認寫在main函數里面。stm32初始化首先進入匯編代碼startup_stm32f429xx.s&#xff0c;在匯編代碼中Reset_Handler&#xff08;復位中斷服務程序&#xff09;里面先調用了Syste…

線上書店訂購系統小組作業匯總

1、數據庫模型圖、er圖1、數據庫模型圖、er圖-CSDN博客 2、網上圖書訂購2、網上圖書訂購-CSDN博客 3、簡單查詢與多表聯合復雜查詢MySQL周內訓參照3、簡單查詢與多表聯合復雜查詢-CSDN博客 4、觸發器-插入-修改-刪除MySQL周內訓參照4、觸發器-插入-修改-刪除-CSDN博客 5、存…

亮相夏季達沃斯論壇天津之夜,國窖1573展現國際化新表達

執筆 | 姜 姜 編輯 | 揚 靈 6月25日-27日&#xff0c;以“未來增長的新前沿”為主題的第十五屆新領軍者年會&#xff0c;即2024年夏季達沃斯論壇盛大舉行。26日晚&#xff0c;在2024年夏季達沃斯論壇天津之夜上&#xff0c;國窖1573作為中國民族品牌的代表&#xff0c;以唯一攜…

vue + Lodop 制作可視化設計頁面 實現打印設計功能(二)

歷史&#xff1a; vue2 Lodop 制作可視化設計頁面 實現打印設計功能&#xff08;一&#xff09; 前言&#xff1a; 之前本來打算用直接拿之前做的vue2版本改改就發的&#xff0c;但考慮到現在主流都是vue3了&#xff0c;所以從這篇文章開始使用vue3來寫&#xff0c;以及最后…

速盾:cdn加速js

CDN加速是一種將網站內容分布到全球各地的服務器上來提高網站訪問速度和穩定性的技術手段。CDN即內容分發網絡&#xff0c;其核心原理是將靜態資源&#xff08;例如圖片、CSS、JavaScript文件等&#xff09;緩存到離用戶最近的服務器節點上&#xff0c;使用戶可以更快地獲取網站…

三相LCL濾波型PWM逆變器仿真設計

參考并網電流外環電容電流前饋內環的雙閉環控制結構&#xff0c;在光伏和風力發電網側變換器中的應用&#xff0c;可以顯著提高系統的穩定性和效率。在并網電流外環中&#xff0c;通過檢測電網電流并與其參考值進行比較&#xff0c;可以得到一個電流誤差信號。這個電流誤差信號…

MySQL基礎查詢與復雜查詢

基礎查詢 1、查詢用戶信息&#xff0c;僅顯示用戶的姓名與手機號&#xff0c;用中文顯示列名。中文顯示姓名列與手機號列。 2、根據商品名稱進行模糊查詢&#xff0c;模糊查詢需要可以走索引&#xff0c;需要給出explain語句。使用explain測試給出的查詢語句&#xff0c;需要顯…

程序員職業發展指南,如何選擇適合自己的就業方向?

隨著科技的發展和數字化時代的到來&#xff0c;程序員是IT行業中的熱門職業。尤其是近幾年移動互聯網的迅速發展&#xff0c;IT人才更是緊缺&#xff0c;越來越多的人加入程序員這個行列。 從事程序員工作&#xff0c;如何接項目呢&#xff1f;YesPMP是一個專注于互聯網外包的平…

【知識學習】闡述Unity3D中動畫渲染的概念及使用方法示例

Unity3D中的卡通渲染&#xff08;Cartoon Rendering&#xff09;是一種渲染技術&#xff0c;它模仿傳統手繪動畫或漫畫的視覺效果。這種渲染風格通常具有鮮明的顏色、清晰的輪廓線和簡化的光影效果&#xff0c;常用于制作動畫、游戲和其他視覺媒體。 卡通渲染的基本概念 輪廓…

<sa8650>QCX ISP Tuning 使用詳解 — Tuning前置條件

<sa8650>QCX ISP Tuning 使用詳解 — Tuning前置條件 一 如何安裝 Qualcomm Chromatix? 攝像頭校準工具二 如何使用 Qualcomm Chromatix? tuning工具創建tuning項目2.1 創建工程前提依賴2.2 創建工程2.3 添加場景2.4 編輯區域觸發器三 如何創建Tuning 樹一 如何安裝 Qualco…

postman教程-22-Newman結合Jenkins執行自動化測試

上一小節我們學習了Postman Newman運行集合生成測試報告的方法&#xff0c;本小節我們講解一下Postman Newman結合Jenkins執行自動化測試的方法。 在軟件開發過程中&#xff0c;持續集成&#xff08;CI&#xff09;是一種實踐&#xff0c;旨在通過自動化的測試和構建過程來頻繁…