主頁 > 後端開發 > 淺析synchronized鎖升級的原理與實作

淺析synchronized鎖升級的原理與實作

2023-07-12 07:54:25 後端開發

背景

在多執行緒編程中,執行緒同步是一個關鍵的概念,它確保了多個執行緒對共享資源的安全訪問,Java中的synchronized關鍵字是一種常用的執行緒同步機制,它不僅提供了互斥訪問的功能,還具備鎖升級的特性,本文將深入探討synchronized的鎖升級原理和實作方式,
在jdk1.5(包含)版本之前,因為加鎖和釋放鎖的程序JVM的底層都是由作業系統mutex lock來實作的,其中會涉及背景關系的切換(即用戶態和內核態的轉換),性能消耗極其高,所以在當時synchronized鎖是公認的重量級鎖,
后來JVM開發團隊為解決性能問題,在jdk1.5版本中加入了JUC并發包,包下開發了很多Lock相關的鎖,來解決同步的性能問題,同時也開始在后續的迭代版本中對synchronized鎖不斷的進行優化來提高性能,比如在jdk1.6版本中就引入了“偏向鎖”和“輕量級鎖”,通過鎖的升級來解決不同并發場景下的性能問題,
通常用使用synchronized方式加鎖影響性能,主要原因如下:

  1. 加鎖解鎖依賴JVM層的的額外操作完成,
  2. 重量級鎖是通過作業系統對執行緒的掛起和恢復來實作,涉及內核態和用戶態的切換

需要儲備的知識:java物件的記憶體布局

注意:本文代碼所使用的JDK版本是1.8,JVM虛擬機是64位的HotSpot實作為準,

鎖的用法

synchronized是java的同步關鍵字,可以使共享資源串行的執行,避免多執行緒競爭導致的執行結果錯誤,使用方法有以下三種,

  1. 作用在類的普通方法(非靜態方法)上,鎖的是當前物件實體,
public synchronized void lockInstance() {
    System.out.println("鎖的是當前物件實體");
}
  1. 作用在類的靜態方法上,鎖的是當前類class,
public synchronized static void lockClass() {
    System.out.println("鎖的是當前類class");
}
  1. 作用在代碼塊上,鎖的是指定的物件實體,
public void lockObject(Object obj) {
    synchronized (obj) {
        System.out.println("鎖的是指定的物件實體obj");
    }
}

原理分析

通過以上的用法,我們可以看到synchronized使用起來很簡單,那它究竟是怎么做到執行緒間互斥訪問的呢,底層原理及實作是怎樣的呢,接下來我們一一解答,
前一篇文章寫了java物件的記憶體布局,里面有一個關于物件頭Markword存盤的內容表格,在synchronized鎖的使用程序中就用到了,如下圖所示,
image.png

鎖的狀態

在jdk1.5版本(包含)之前,鎖的狀態只有兩種狀態:“無鎖狀態”和“重量級鎖狀態”,只要有執行緒訪問共享資源物件,則鎖直接成為重量級鎖,jdk1.6版本后,對synchronized鎖進行了優化,新加了“偏向鎖”和“輕量級鎖”,用來減少背景關系的切換以提高性能,所以鎖就有了4種狀態,

  1. 無鎖

對于共享資源,不涉及多執行緒的競爭訪問,

  1. 偏向鎖

共享資源首次被訪問時,JVM會對該共享資源物件做一些設定,比如將物件頭中是否偏向鎖標志位置為1,物件頭中的執行緒ID設定為當前執行緒ID(注意:這里是作業系統的執行緒ID),后續當前執行緒再次訪問這個共享資源時,會根據偏向鎖標識跟執行緒ID進行比對是否相同,比對成功則直接獲取到鎖,進入臨界區域(就是被鎖保護,執行緒間只能串行訪問的代碼),這也是synchronized鎖的可重入功能,

  1. 輕量級鎖

當多個執行緒同時申請共享資源鎖的訪問時,這就產生了競爭,JVM會先嘗試使用輕量級鎖,以CAS方式來獲取鎖(一般就是自旋加鎖,不阻塞執行緒采用回圈等待的方式),成功則獲取到鎖,狀態為輕量級鎖,失敗(達到一定的自旋次數還未成功)則鎖升級到重量級鎖,

  1. 重量級鎖

如果共享資源鎖已經被某個執行緒持有,此時是偏向鎖狀態,未釋放鎖前,再有其他執行緒來競爭時,則會升級到重量級鎖,另外輕量級鎖狀態多執行緒競爭鎖時,也會升級到重量級鎖,重量級鎖由作業系統來實作,所以性能消耗相對較高,
這4種級別的鎖,在獲取時性能消耗:重量級鎖 > 輕量級鎖 > 偏向鎖 > 無鎖,

鎖升級

鎖升級是針對于synchronized鎖在不同競爭條件下的一種優化,根據鎖在多執行緒中競爭的程度和狀態,synchronized鎖可在無鎖、偏向鎖、輕量級鎖和重量級鎖之間進行流轉,以降低獲取鎖的成本,提高獲取鎖的性能,
通過下面這個命令,可以看到所有JVM引數的默認值,

java -XX:+PrintFlagsFinal -version

鎖升級程序

  1. 當JVM啟動后,一個共享資源物件直到有執行緒第一個訪問時,這段時間內是處于無鎖狀態,物件頭的Markword里偏向鎖標識位是0,鎖標識位是01,

image.png

  1. 從jdk1.6之后,JVM有兩個默認引數是開啟的,-XX:+UseBiasedLocking(表示啟用偏向鎖,想要關閉偏向鎖,可添加JVM引數:-XX:-UseBiasedLocking),-XX:BiasedLockingStartupDelay=4000(表示JVM啟動4秒后打開偏向鎖,也可以自定義這個延遲時間,如果設定成0,那么JVM啟動就打開偏向鎖),

當一個共享資源首次被某個執行緒訪問時,鎖就會從無鎖狀態升級到偏向鎖狀態,偏向鎖會在Markword的偏向執行緒ID里存盤當前執行緒的作業系統執行緒ID,偏向鎖標識位是1,鎖標識位是01,此后如果當前執行緒再次進入臨界區域時,只比較這個偏向執行緒ID即可,這種情況是在只有一個執行緒訪問的情況下,不再需要作業系統的重量級鎖來切換背景關系,提供程式的訪問效率,
另外需要注意的是,由于硬體資源的不斷升級,獲取鎖的成本隨之下降,jdk15版本后默認關閉了偏向鎖,
如果未開啟偏向鎖(或者在JVM偏向鎖延遲時間之前)有執行緒訪問共享資源則直接由無鎖升級為輕量級鎖,請看第3步,
image.png

  1. 如果未開啟偏向鎖(或者在JVM偏向鎖延遲時間之前),有執行緒訪問共享資源則直接由無鎖升級為輕量級鎖,開啟偏向執行緒鎖后,并且當前共享資源鎖已經是偏向鎖時,再有第二個執行緒訪問共享資源鎖時,此時鎖可能升級為輕量級鎖,也可能還是偏向鎖狀態,因為這取決于執行緒間的競爭情況,如有沒有競爭,那么偏向鎖的效率更高(因為頻繁的鎖競爭會導致偏向鎖的撤銷和升級到輕量級鎖),繼續保持偏向鎖,如果有競爭,則鎖狀態會從偏向鎖升級到輕量級鎖,這種情況下輕量級鎖效率會更高,

當第二個執行緒嘗試獲取偏向鎖失敗時,偏向鎖會升級為輕量級鎖,此時,JVM會使用CAS自旋操作來嘗試獲取鎖,如果成功則進入臨界區域,否則升級為重量級鎖,
輕量級鎖是在當前執行緒的堆疊幀中建立一個名為鎖記錄(Lock Record)的空間,嘗試拷貝鎖物件頭的Markword到堆疊幀的Lock Record,若拷貝成功,JVM將使用CAS操作嘗試將物件頭的Markword更新為指向Lock Record的指標,并將Lock Record里的owner指標指向物件頭的Markword,若拷貝失敗,若當前只有一個等待執行緒,則可通過自旋繼續嘗試, 當自旋超過一定的次數,或者一個執行緒在持有鎖,一個執行緒在自旋,又有第三個執行緒來訪問時,輕量級鎖就會膨脹為重量級鎖,
image.png

  1. 當輕量級鎖獲取鎖失敗時,說明有競爭存在,輕量級鎖會升級為重量級鎖,此時,JVM會將執行緒阻塞,直到獲取到鎖后才能進入臨界區域,底層是通過作業系統的mutex lock來實作的,每個物件指向一個monitor物件,這個monitor物件在堆中與鎖是關聯的,通過monitorenter指令插入到同步代碼塊在編譯后的開始位置,monitorexit指令插入到同步代碼塊的結束處和例外處,這兩個指令配對出現,JVM的執行緒和作業系統的執行緒是對應的,重量級鎖的Markword里存盤的指標是這個monitor物件的地址,作業系統來控制內核態中的執行緒的阻塞和恢復,從而達到JVM執行緒的阻塞和恢復,涉及內核態和用戶態的切換,影響性能,所以叫重量級鎖,

image.png
鎖升級簡要步驟如下所示
image.png

注意:圖中無鎖到偏向鎖這不是升級,是在偏向鎖打開后,物件默認是偏向狀態,沒有從無鎖升級到偏向鎖的程序,偏向鎖未開啟,會直接從無鎖升級到輕量級鎖,偏向鎖開啟時,會從偏向鎖升級到輕量級鎖,

鎖升級細化流程
image.png
下面我們結合代碼看下各狀態鎖的升級場景
需要添加JOL包,用來查看物件頭資訊

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.17</version>
</dependency>

無鎖 --> 輕量級鎖

無鎖升級到輕量級鎖有兩種情況

  1. 第一種,關閉偏向鎖,執行時增加JVM引數:-XX:-UseBiasedLocking
public void lockUpgradeTest1() {
    Object obj = new Object();
    System.out.println("未開啟偏向鎖,物件資訊");
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    synchronized (obj) {
        System.out.println("已獲取到鎖資訊");
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
    System.out.println("已釋放鎖資訊");
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}

運行結果:

未開啟偏向鎖,物件資訊
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

已獲取到鎖資訊
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000000336f2b0 (thin lock: 0x000000000336f2b0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

已釋放鎖資訊
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

采用JOL輸出的物件頭markword是16進制的,需要轉換成64位的2進制來看,

關閉偏向鎖的情況下,物件加鎖之前,物件頭markword是0x0000000000000001換算成二進制末尾三位是001,即偏向鎖標識為0,鎖標識為01,是無鎖狀態,
加鎖成功后,執行同步代碼塊,物件頭markword是0x000000000336f2b0換算成二進制末尾兩位是00,即鎖標識為00,是輕量級鎖狀態,
最后在執行完同步代碼塊后,再次列印物件頭資訊,物件頭markword是0x0000000000000001換算成二進制末尾三位是001,即偏向鎖標識為0,鎖標識為01,是無鎖狀態,說明輕量級鎖在執行完同步代碼塊后進行了鎖的釋放,

  1. 第二種,默認情況下,在偏向鎖延遲時間之前獲取鎖
public void lockUpgradeTest2() {
    Object obj = new Object();
    System.out.println("開啟偏向鎖,偏向鎖延遲時間前,物件資訊");
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    synchronized (obj) {
        System.out.println("已獲取到鎖資訊");
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
    System.out.println("開啟偏向鎖,已釋放鎖資訊");
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}

運行結果:

開啟偏向鎖,偏向鎖延遲時間前,物件資訊
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

已獲取到鎖資訊
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000000316f390 (thin lock: 0x000000000316f390)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

開啟偏向鎖,已釋放鎖資訊
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

使用默認的偏向鎖配置,JVM啟動4秒后才啟動偏向鎖,所以JVM啟動時就列印并獲取鎖資訊,效果跟第一種一樣,markword解釋同上,

偏向鎖 --> 輕量級鎖

public void lockUpgradeTest3() {
    // JVM默認4秒后才可以偏向鎖,所以這里休眠5秒,鎖物件就是偏向鎖了
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    Object object = new Object();
    Thread t1 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "開啟偏向鎖,偏向鎖延遲時間后,物件資訊" + ClassLayout.parseInstance(object).toPrintable());
        synchronized (object) {
            System.out.println(Thread.currentThread().getName() + "已獲取到鎖資訊" + ClassLayout.parseInstance(object).toPrintable());
        }
        System.out.println(Thread.currentThread().getName() + "開啟偏向鎖,已釋放鎖資訊" + ClassLayout.parseInstance(object).toPrintable());
    }, "t1");
    t1.start();
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    Thread t2 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "開啟偏向鎖,偏向鎖延遲時間后,物件資訊" + ClassLayout.parseInstance(object).toPrintable());
        synchronized (object) {
            System.out.println(Thread.currentThread().getName() + "已獲取到鎖資訊" + ClassLayout.parseInstance(object).toPrintable());
        }
        System.out.println(Thread.currentThread().getName() + "開啟偏向鎖,已釋放鎖資訊" + ClassLayout.parseInstance(object).toPrintable());
    }, "t2");
    t2.start();
}

運行結果有兩種可能:
第一種:

t1開啟偏向鎖,偏向鎖延遲時間后,物件資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t1已獲取到鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001fbb3005 (biased: 0x000000000007eecc; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t1開啟偏向鎖,已釋放鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001fbb3005 (biased: 0x000000000007eecc; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t2開啟偏向鎖,偏向鎖延遲時間后,物件資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001fbb3005 (biased: 0x000000000007eecc; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t2已獲取到鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020f7f2d0 (thin lock: 0x0000000020f7f2d0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t2開啟偏向鎖,已釋放鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

啟動JVM,默認4秒后開啟偏向鎖,這里休眠了5秒,保證JVM開啟偏向鎖,然后創建了物件,物件頭markword資訊0x0000000000000005換算成二進制后三位是101,偏向鎖標識為1,鎖標識為01,為偏向鎖狀態,偏向執行緒ID是0,說明這是初始偏向狀態,t1先獲取到鎖進入同步代碼塊后,markword變成0x000000001fbb3005轉換成二進制:11111101110110011000000000101(前面補0直到長度是64位),末尾三位依然是101,還是偏向鎖,只不過前54位將對應的作業系統執行緒ID寫到偏向執行緒ID里了,同步代碼塊執行完成后,markword依然沒變,說明偏向鎖狀態不會自動釋放鎖,需要等其他執行緒來競爭鎖才走偏向鎖撤銷流程,t2執行緒開始執行時鎖物件markword是0x000000001fbb3005,說明偏向鎖偏向了t1對應的作業系統執行緒,等t1釋放鎖,t2獲取到鎖進入同步代碼塊時,物件鎖markword是0x0000000020f7f2d0,換算成二進制:100000111101111111001011010000(前面補0直到長度是64位),末尾兩位是00,鎖已經變成輕量級鎖了,鎖的指標也變了,是指向t2執行緒堆疊中的Lock Record記錄了,等t2執行緒釋放鎖后,物件鎖末尾是001,說明是無鎖狀態了,輕量級鎖會自動釋放鎖,
第二種:

t1開啟偏向鎖,偏向鎖延遲時間后,物件資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t1已獲取到鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002031d805 (biased: 0x0000000000080c76; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t1開啟偏向鎖,已釋放鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002031d805 (biased: 0x0000000000080c76; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t2開啟偏向鎖,偏向鎖延遲時間后,物件資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002031d805 (biased: 0x0000000000080c76; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t2已獲取到鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002031d805 (biased: 0x0000000000080c76; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t2開啟偏向鎖,已釋放鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002031d805 (biased: 0x0000000000080c76; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t1執行緒正常獲取鎖,鎖狀態是偏向鎖,執行完同步代碼塊后鎖還是偏向鎖,說明偏向鎖不隨執行同步代碼塊的結束而釋放鎖,t2執行緒拿到鎖是偏向鎖,獲取到鎖依然是偏向鎖,而沒有升級到輕量級鎖,說明執行緒間鎖沒有競爭的情況下,依然保持偏向鎖,這樣效率會更高,

偏向鎖 --> 重量級鎖

public void lockUpgradeTest4() {
    // JVM默認4秒后才可以偏向鎖,所以這里休眠5秒,鎖物件就是偏向鎖了
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    Object object = new Object();
    Thread t1 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "加鎖前物件資訊" + ClassLayout.parseInstance(object).toPrintable());
        synchronized (object) {
            System.out.println(Thread.currentThread().getName() + "已獲取到鎖資訊" + ClassLayout.parseInstance(object).toPrintable());
            try {
                // 讓t2執行緒啟動后并競爭鎖
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println(Thread.currentThread().getName() + "已釋放鎖資訊" + ClassLayout.parseInstance(object).toPrintable());
    }, "t1");
    t1.start();
    try {
        // 讓t1執行緒先啟動并拿到鎖
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    Thread t2 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "加鎖前物件資訊" + ClassLayout.parseInstance(object).toPrintable());
        synchronized (object) {
            System.out.println(Thread.currentThread().getName() + "已獲取到鎖資訊" + ClassLayout.parseInstance(object).toPrintable());
        }
        System.out.println(Thread.currentThread().getName() + "已釋放鎖資訊" + ClassLayout.parseInstance(object).toPrintable());
    }, "t2");
    t2.start();
}

運行結果:

t1加鎖前物件資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t1已獲取到鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020993805 (biased: 0x000000000008264e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t2加鎖前物件資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020993805 (biased: 0x000000000008264e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t2已獲取到鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d2c6c2a (fat lock: 0x000000001d2c6c2a)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t1已釋放鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d2c6c2a (fat lock: 0x000000001d2c6c2a)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t2已釋放鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

程式先休眠5秒保證偏向鎖開啟,然后t1執行緒先啟動并成功獲取到鎖,t1獲取到鎖之前物件markword是偏向狀態但偏向執行緒ID是0,t1獲取到鎖之后markword里有了偏向執行緒ID,也就是t1執行緒對應的作業系統執行緒ID,t2執行緒獲取鎖之前,物件鎖已經是偏向鎖并偏向t1對應的執行緒,t2執行緒獲取鎖時t1已經持有鎖并沒有釋放,鎖未釋放其他執行緒再競爭鎖,這時會發生鎖升級,由偏向鎖升級成重量級鎖,所以t1釋放鎖跟t2獲取到鎖時,物件頭的markword是0x000000001d2c6c2a,轉換成二進制11101001011000110110000101010(前后補0到夠64位),最后兩位是10,標識重量級鎖,前面的62存的是指向堆中跟monitor對應鎖物件的指標,

輕量級鎖 --> 重量級鎖

public void lockUpgradeTest5() {
        // JVM默認4秒后才可以偏向鎖,所以這里休眠5秒,鎖物件就是偏向鎖了
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        Object object = new Object();
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "加鎖前物件資訊" + ClassLayout.parseInstance(object).toPrintable());
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + "已獲取到鎖資訊" + ClassLayout.parseInstance(object).toPrintable());
                try {
                    // 讓t2執行緒啟動后并競爭鎖
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println(Thread.currentThread().getName() + "已釋放鎖資訊" + ClassLayout.parseInstance(object).toPrintable());
        }, "t1");
        t1.start();
        try {
            // 讓t1執行緒先啟動并拿到鎖
            t1.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        Thread t2 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "加鎖前物件資訊" + ClassLayout.parseInstance(object).toPrintable());
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + "已獲取到鎖資訊" + ClassLayout.parseInstance(object).toPrintable());
            }
            System.out.println(Thread.currentThread().getName() + "已釋放鎖資訊" + ClassLayout.parseInstance(object).toPrintable());
        }, "t2");
        t2.start();
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "加鎖前物件資訊" + ClassLayout.parseInstance(object).toPrintable());
                synchronized (object) {
                    System.out.println(Thread.currentThread().getName() + "已獲取到鎖資訊" + ClassLayout.parseInstance(object).toPrintable());
                }
                System.out.println(Thread.currentThread().getName() + "已釋放鎖資訊" + ClassLayout.parseInstance(object).toPrintable());
            }, "t3_" + i).start();
        }

    }

運行結果:

注意:這里t2執行緒也有可能獲取到的鎖是偏向鎖,無競爭的情況下,這取決于執行緒的執行情況,這里我們以t2獲取到輕量級鎖,講解輕量級鎖升級到重量級鎖的程序,

t1加鎖前物件資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t1已獲取到鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020e1b805 (biased: 0x000000000008386e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t1已釋放鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020e1b805 (biased: 0x000000000008386e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t2加鎖前物件資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020e1b805 (biased: 0x000000000008386e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t3_0加鎖前物件資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020e1b805 (biased: 0x000000000008386e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t3_1加鎖前物件資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020e1b805 (biased: 0x000000000008386e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t2已獲取到鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002270f1d0 (thin lock: 0x000000002270f1d0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t3_2加鎖前物件資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002270f1d0 (thin lock: 0x000000002270f1d0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t3_1已獲取到鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d0356da (fat lock: 0x000000001d0356da)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t2已釋放鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d0356da (fat lock: 0x000000001d0356da)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t3_1已釋放鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d0356da (fat lock: 0x000000001d0356da)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t3_0已獲取到鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d0356da (fat lock: 0x000000001d0356da)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t3_0已釋放鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d0356da (fat lock: 0x000000001d0356da)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t3_2已獲取到鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d0356da (fat lock: 0x000000001d0356da)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t3_2已釋放鎖資訊java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001d0356da (fat lock: 0x000000001d0356da)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t1執行緒加鎖執行代碼塊后,鎖狀態是偏向鎖,t1在同步代碼塊里讓休眠了3秒目的是讓t2執行緒起來并競爭鎖,然后t1執行緒執行完同步代碼塊,鎖狀態還是偏向鎖,這時候for回圈的3個執行緒也啟動起來爭搶所,t2執行緒先啟動獲取到鎖為輕量級鎖,for回圈里啟動的3個執行緒在獲取同步鎖前,我們看到列印的鎖狀態有的是偏向鎖、有的是輕量級鎖,說明在t2執行緒加鎖成功前還是偏向鎖,t2加鎖后就成輕量級鎖了,然后for回圈的3個執行緒相繼獲取到鎖,發現鎖已經升級到重量級鎖了,物件頭markword是0x000000001d0356da,換成二進制:11101000000110101011011011010(前面補齊0到夠64位),末尾兩位鎖狀態是10,表示重量級鎖,

底層實作

本文開頭講的synchronized在代碼層的用法有三種,鎖物件實體、鎖類class、鎖指定實體物件,我們可以將以下代碼編譯成class后,在反編譯出來看看JVM指令碼是怎樣的,

public class Synchronized1 {
    public static void main(String[] args) {
        System.out.println("test Synchronized1");
    }
    public synchronized void lockInstance() {
        System.out.println("鎖的是當前物件實體");
    }

    public synchronized static void lockClass() {
        System.out.println("鎖的是當前類class");
    }

    public void lockObject(Object obj) {
        synchronized (obj) {
            System.out.println("鎖的是指定的物件實體obj");
        }
    }
}

通過javap命令反編譯class檔案,
image.png
我本文的例子使用的命令是這樣的:

javap -c -v -l Synchronized1.class

我們主要關注那3個方法的JVM指令碼,
image.png

image.png

image.png
在方法(非靜態方法鎖的是物件,靜態方法鎖的是類class)上加synchronized關鍵字,是通過在access_flags中設定ACC_SYNCHRONIZED標志來實作,synchronized使用在代碼塊上,是通過monitorenter和monitorexit指令來實作,
重量鎖底層最終是依靠作業系統的阻塞和喚醒來實作,每個物件有一個監視器鎖(monitor),在 Java 虛擬機(HotSpot)中,monitor 是基于 C++的ObjectMonitor實作,物件鎖里有計數器、重入次數、等待鎖的執行緒串列、存盤該monitor的物件、擁有monitor的執行緒等引數,虛擬機是通過進入和退出monitor來實作同步,monitorenter指令是在編譯后插入到同步代碼塊的開始位置,monitorexit是插入到方法結束處和例外處,根據虛擬機規范的要求,在執行monitorenter指令時,首先要去嘗試獲取物件的鎖,如果這個物件沒被鎖定,或者當前執行緒已經擁有了那個物件的鎖,把鎖的計數器加1;相應地,在執行monitorexit指令時會將鎖計數器減1,當計數器被減到0時,鎖就釋放了(注意執行monitorexit的執行緒必須是已經獲得monitor物件鎖的執行緒),如果獲取物件鎖失敗了,那當前執行緒就要阻塞等待,直到物件鎖被另一個執行緒釋放然后由作業系統喚醒等到鎖的執行緒繼續競爭鎖直到獲取到鎖為止,

總結

synchronized關鍵字是Java中常用的執行緒同步機制,其具備鎖升級的特性,可以根據競爭的程度和鎖的狀態進行自動切換,鎖升級通過無鎖、偏向鎖、輕量級鎖和重量級鎖四種狀態的轉換,以提高并發性能,在實際開發中,我們應該了解鎖升級的原理,并根據具體場景進行合理的鎖設計和優化,以實作高效且安全的多執行緒編程,
隨著jdk版本的升級,JVM底層的實作持續優化,版本的不同伴隨著引數使用及默認配置的不同,但總之JVM層對synchronized的優化效率越來越高,所以不應該再把synchronized同步當重量級鎖來看,
其實本文介紹了鎖升級的主要程序,關于synchronized還有鎖消除、鎖粗化的優化手段,使得synchronized性能在某些場景應用下,可能會比JUC包底下的Lock相關鎖效率更高,
另外synchronized鎖原理、優化、使用遠不止本文說的這么多,感興趣的可進一步探索,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/557009.html

標籤:其他

上一篇:Maven專案中使用Mybatis框架

下一篇:返回列表

標籤雲
其他(162377) Python(38274) JavaScript(25530) Java(18294) C(15239) 區塊鏈(8275) C#(7972) AI(7469) 爪哇(7425) MySQL(7294) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5876) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4615) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2438) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) HtmlCss(1995) .NET技术(1986) 功能(1967) Web開發(1951) C++(1942) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1882) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • 淺析synchronized鎖升級的原理與實作

    # 背景 在多執行緒編程中,執行緒同步是一個關鍵的概念,它確保了多個執行緒對共享資源的安全訪問。Java中的synchronized關鍵字是一種常用的執行緒同步機制,它不僅提供了互斥訪問的功能,還具備鎖升級的特性。本文將深入探討synchronized的鎖升級原理和實作方式。 在jdk1.5(包含)版本之前 ......

    uj5u.com 2023-07-12 07:54:25 more
  • Maven專案中使用Mybatis框架

    一 .準備一個空的Maven專案。 二. 配置pom檔案,引入相關依賴。 <!--版本建議換成提示的更安全的版本--> <!-- mybatis插件 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifa ......

    uj5u.com 2023-07-12 07:54:15 more
  • [滲透測驗]—4.2 Web應用安全漏洞

    在本節中,我們將學習OWASP(開放網路應用安全專案)發布的十大Web應用安全漏洞。OWASP十大安全漏洞是對Web應用安全風險進行評估的標準,幫助開發者和安全工程師了解并防范常見的安全威脅。 ### 1. A1 - 注入(Injection) **概念**:注入漏洞發生在應用程式將不可信的資料作為 ......

    uj5u.com 2023-07-12 07:54:06 more
  • 跨越HTTP無狀態邊界:Cookie與Session在Django中的實戰應用

    **本文深入探索了Django中的Cookie和Session,決議了如何應對HTTP協議的無狀態性問題,說明其基礎概念,分析作業原理,并討論何時應選擇使用Cookie或Session。文章進階部分,提出高效管理Cookie和Session,以及如何利用它們進行用戶身份驗證。** ## HTTP協議 ......

    uj5u.com 2023-07-12 07:54:00 more
  • RequestContextHolder跨執行緒獲取不到requests請求物件的解決方

    # 一、前言 最近在做一個專案,有個比較耗時的操作是啟用執行緒進行異步操作,當時在啟用的執行緒時,突然發現子執行緒無法獲取父執行緒中的HttpServletRequest請求物件,因為是第一次遇到這種問題,所以記錄一下解決方案。 # 二、問題模擬 在這里,我們簡單模擬一下出現的問題。我們首先撰寫一個簡單的h ......

    uj5u.com 2023-07-12 07:53:50 more
  • 仿冒社交APP如何竊取資訊后展開勒索詐騙

    ## 起因 最近某論壇有個小伙伴求助,說自己安裝了一款 APP 后,自己的通訊錄、短信、相冊都被竊取了,進而要挾他轉賬匯款。 大概情況如下: 首先是在某社交 APP 群組中加他,好友通過后的聊天如下: ![file](https://img2023.cnblogs.com/other/606533/ ......

    uj5u.com 2023-07-12 07:51:16 more
  • 【經典爬蟲案例】用Python爬取微博熱搜榜!

    [toc] # 一、爬取目標 您好,我是[@馬哥python說](https://www.zhihu.com/people/13273183132),一名10年程式猿。 本次爬取的目標是: [微博熱搜榜](https://s.weibo.com/top/summary?cate=realtimeho ......

    uj5u.com 2023-07-12 07:44:04 more
  • 【調制解調】DSB 雙邊帶調幅

    學習數字信號處理演算法時整理的學習筆記。本篇介紹 DSB 雙邊帶調幅信號的調制與解調,內附全套 MATLAB 代碼。 ......

    uj5u.com 2023-07-11 08:21:46 more
  • 為什么使用ioutil.ReadAll 函式需要注意

    # 1. 引言 當我們需要將資料一次性加載到記憶體中,`ioutil.ReadAll` 函式是一個方便的選擇,但是`ioutil.ReadAll` 的使用是需要注意的。 在這篇文章中,我們將首先對`ioutil.ReadAll`函式進行基本介紹,之后會介紹其存在的問題,以及引起該問題的原因,最后給出了 ......

    uj5u.com 2023-07-11 08:21:41 more
  • 為什么使用ioutil.ReadAll 函式需要注意

    # 1. 引言 當我們需要將資料一次性加載到記憶體中,`ioutil.ReadAll` 函式是一個方便的選擇,但是`ioutil.ReadAll` 的使用是需要注意的。 在這篇文章中,我們將首先對`ioutil.ReadAll`函式進行基本介紹,之后會介紹其存在的問題,以及引起該問題的原因,最后給出了 ......

    uj5u.com 2023-07-11 08:20:28 more