多線程-sychronized鎖膨脹

sychronized

什麼是cas

  1. cas的定義:在操作系統中,CAS通常代表“Compare And Swap”,它是一種原子操作,用於解決併發訪問的問題。具體地說,CAS操作會比較並交換一個內存位置的值,只有當內存位置的值與期望的值相等時,纔會將新值寫入該位置。如果內存位置的值與期望的值不相等,則說明這個內存位置已經被其他線程修改,此時CAS操作不會執行任何修改,並返回失敗狀態。

  2. cas涉及的指令:cpmxchg,他通常和自旋鎖(spin lock)配合使用,這個自旋鎖在os/c++中實現略有不同具體如下,看os就行

  3. linux內核下spin lock

    typedef struct spinlock {
            union {
                    atomic_t count;
            };
    } spinlock_t;
    
    
  4. c++中spin lock

    
    #include <atomic>  // 頭文件
    
    class SpinLock {
    public:
        SpinLock() { flag.clear(std::memory_order_release); }
        
        void lock() {
            while (flag.test_and_set(std::memory_order_acquire)) ;  // 忙等待
        }
        
        void unlock() {
            flag.clear(std::memory_order_release);
        }
    
    private:
        std::atomic_flag flag;
    };
    
    

在多線程程序中,CAS可以用來保證數據的一致性和正確性,防止出現競態條件等問題。因此,CAS操作是許多併發編程框架和實現中常用的技術之一。

什麼是公平鎖,什麼是公平鎖,先說reentrantlock

  1. 結論:公平與非公平鎖,其區別在於搶鎖之前是否排隊,一旦入隊,永遠排隊

    1. 公平鎖搶鎖邏輯:線程進入-》調用lock方法-》然後tryAcquire(這裏具體如下 獲取當前線程,判斷鎖的狀態是否是0,判斷鎖是否是否被人持有-》如果沒人持有看有沒有排隊的,若有人排隊自己也去排隊;如果鎖被人持有了則加鎖失敗,自己去入隊,如果自己是head,則自選取再次拿鎖嘗試)

    1683364731405

  2. 非公平鎖搶鎖邏輯:線程進入-》調用lock方法-》然後tryAcquire(這裏具體行爲如下:獲取當前線程-》獲取鎖狀態是否爲0-》判斷是否被人持有,如果被人持有就自旋等待,外一成功就拿到了鎖,如果失敗了,進入隊列,此時會看上一個節點是不是head,如果是在自選一次嘗試拿鎖,還拿不到就排隊去)

    1683364794024

  3. 這是別人畫的圖,講述整個流程

    1683365951524

  4. 隊列:剛纔說到入隊,隊列的數據結構如下

    public class Node{
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    }
    

關於synchronized是否是公平鎖

​ 1. 像上面說的,是否公平就看入隊前是否搶鎖,入隊之後永遠排隊

鎖的基礎知識

​ 首先介紹一下鎖,和內存分佈

  1. 無鎖:分爲101 無鎖可偏向,或者偏向鎖,如果前52個字符不是0,則代表偏向鎖,前52位是偏向線程id,如果後三位是001則代表無鎖,下面是對應的幾種情況

    1683510858956

    1683510904179

    1683510915818

  2. 輕量鎖 00,一個64位,前62位是一個指針,看後兩位即可

  3. 重新加鎖和重入鎖的區別

    1683511031814

偏向鎖膨脹的具體過程,結合c++代碼

  1. 偏向鎖的獲取過程,首先有一個鎖對象lock,在當前線程下創建lockrecord,然後判斷是否鎖是自己持有,是否過期,然後拿到鎖,這就是偏向鎖,當第一次之後獲取,比如第二次獲取,還是會建立lockrecord,判斷是否自己持有是否過期,如果滿足就拿到鎖不需要cas,所以性能最好,釋放的過程就是從棧中找到對應的lockrecord釋放掉

    1683511243917

  2. 輕量鎖:假設t1釋放了偏向鎖,t2進來拿鎖,此時需要先進行偏向撤銷(簡單來說,撤銷偏向,置成無鎖001)

    其內存變化如下:(看c++文件重新寫)

    1683512278579

  1. 此時如果t2釋放鎖,t3來了,此時t3先生成無鎖的markword 00,然後lockrocrd中displacedword 設置成markword,之後cas判斷當前對象頭和內存中對應markword是否相等,如果相等就吧對象頭中的markword修改爲一個指針指向lockrecord,同時把對象頭中markword的後兩位改成00;

    1683512814826

    1683512887818

    1683513156764

     	4.  輕量鎖膨脹爲重量鎖:t3修改之前,t4進來完成了t3要完成的操作,t3就是加鎖失敗這個時候就會膨脹,涉及到 pthredmutex;
    
    1. 批量重偏向,這裏涉及到連續偏向撤銷20次,40次的倆個閾值問題:這裏說結論:當撤銷偏向鎖閾值超過 20 次後,jvm 會這樣覺得,我是不是偏向錯了呢,於是會在給這些對象加鎖時重新偏向至t2;當一個偏向鎖如果撤銷次數到達40的時候就認爲這個對象設計的有問題;那麼JVM會把這個對象所對應的類所有的對象都撤銷偏向鎖;並且新實例化的對象也是不可偏向的

    2. 鎖膨脹的流程圖:

      1683514657692

1683599190865

大概邏輯如上面的圖,這裏文字版;

獲取鎖對象lockee->創建lockrecord->讓lockrecord的obj關聯lockee->獲取lockee的頭信息-》此時判斷是否是偏向鎖(這裏有一些細節,比如初始化01這些看代碼,看了一遍也沒記住)

1.是偏向鎖,獲取當前線程id,看是否偏向自己,如過偏向自己就拿到鎖;如果沒有則看是否關閉偏向鎖,如果關閉則偏向鎖升級爲輕量鎖;如果沒關閉,看是否過期或者偏向別人,無論哪種情況都重新創建一個偏向自己的mark,通過cas設置對象頭,成功就獲得鎖,失敗就升級爲輕量鎖;

2.不是偏向鎖則升級爲輕量鎖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章