愛鋒貝

標(biāo)題: synchronize中的三種鎖的形式在使用應(yīng)用場景上有什么區(qū)別 ... [打印本頁]

作者: 希慕阿巴阿巴    時間: 2023-4-8 08:09
標(biāo)題: synchronize中的三種鎖的形式在使用應(yīng)用場景上有什么區(qū)別 ...
偏向鎖、輕量級鎖、重量級鎖在使用過程中有什么不同呢?

-----------------------------
作者: 李柯    時間: 2023-4-8 09:36
synchronize基礎(chǔ)篇

Java共享模型帶來的線程安全問題

問題分析

兩個線程對初始值為 0 的靜態(tài)變量一個做自增,一個做自減,各做 5000 次,結(jié)果是 0 嗎?
public class SyncDemo {
   
    private static int counter = 0;

    public static void increment() {
        counter++;
    }

    public static void decrement() {
        counter--;
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                increment();
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                decrement();
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();

        //思考: counter=?
        log.info("{}", counter);
    }
getstatic i // 獲取靜態(tài)變量i的值
iconst_1 // 將int常量1壓入操作數(shù)棧
iadd // 自增
getstatic i // 獲取靜態(tài)變量i的值
iconst_1 // 將int常量1壓入操作數(shù)棧
isub // 自減


(, 下載次數(shù): 157)

臨界區(qū)( Critical Section)

//臨界資源
private static int counter = 0;

public static void increment() { //臨界區(qū)
    counter++;
}

public static void decrement() {//臨界區(qū)
    counter--;競態(tài)條件( Race Condition )

【注意】
雖然 java 中互斥和同步都可以采用 synchronized 關(guān)鍵字來完成,但它們還是有區(qū)別的:


synchronize的使用

synchronize加鎖方式

加鎖方式分類

分類具體分類被鎖的對象偽代碼
方法實例方法類的實例對象public synchronize void method(){}
靜態(tài)方法類對象public static synchronize void method(){}
代碼塊實例對象類的實例對象synchronize(this){}
class對象類對象synchronize(SynchronizeDemo.class){}
任意實例對象object實例對象object// String 對象作鎖String lock = "";synchronize(lock){}
解決共享問題方法

public static synchronized void increment() {
    counter++;
}
public static synchronized void decrement() {
    counter--;
}
private static String lock = "";
public static void increment() {
    synchronized (lock){
        counter++;
    }
}
public static void decrement() {
    synchronized (lock) {
        counter--;
    }
}


(, 下載次數(shù): 164)

synchronize高級篇——底層原理

synchronize底層原理

JVM指令層面synchronize實現(xiàn)原理


管程(Monitnor)之MESA模型詳解

管程

管程實現(xiàn)模型

MESA模型



(, 下載次數(shù): 168)

wait()的正確使用姿勢

while(條件不滿足) {
  wait();
}
notify()和notifyAll()分別何時使用

Java的內(nèi)置管程synchronize



(, 下載次數(shù): 174)

Monitnor機(jī)制在Java中的實現(xiàn)

ObjectMonitor() {
    _header       = NULL; //對象頭  markOop
    _count        = 0;  
    _waiters      = 0,   
    _recursions   = 0;   // 鎖的重入次數(shù)
    _object       = NULL;  //存儲鎖對象
    _owner        = NULL;  // 標(biāo)識擁有該monitor的線程(當(dāng)前獲取鎖的線程)
    _WaitSet      = NULL;  // 等待線程(調(diào)用wait)組成的雙向循環(huán)鏈表,_WaitSet是第一個節(jié)點
    _WaitSetLock  = 0 ;   
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ; //多線程競爭鎖會先存到這個單向鏈表中 (FILO棧結(jié)構(gòu))
    FreeNext      = NULL ;
    _EntryList    = NULL ; //存放在進(jìn)入或重新進(jìn)入時被阻塞(blocked)的線程 (也是存競爭鎖失敗的線程)
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;


(, 下載次數(shù): 143)

在獲取鎖時,是將當(dāng)前線程插入到cxq的頭部,而釋放鎖時,默認(rèn)策略(QMode=0)是:如果EntryList為空,則將cxq中的元素按原有順序插入到EntryList,并喚醒第一個線程,也就是當(dāng)EntryList為空時,是后來的線程先獲取鎖。_EntryList不為空,直接從_EntryList中喚醒線程
對象內(nèi)存布局&對象頭詳解

對象內(nèi)存布局

Hotspot虛擬機(jī)中,對象在內(nèi)存中存儲的布局可以分為三塊區(qū)域:


(, 下載次數(shù): 159)

對象頭



(, 下載次數(shù): 163)

HotSpot虛擬機(jī)的對象頭包括:
Mark word是如何記錄鎖狀態(tài)的

Mark Word的結(jié)構(gòu)


32位JVM下的對象結(jié)構(gòu)描述



(, 下載次數(shù): 155)

64位JVM下的對象結(jié)構(gòu)描述



(, 下載次數(shù): 173)

Mark Word中鎖標(biāo)記枚舉

enum { locked_value             = 0,    //00 輕量級鎖
         unlocked_value           = 1,   //001 無鎖
         monitor_value            = 2,   //10 監(jiān)視器鎖,也叫膨脹鎖,也叫重量級鎖
         marked_value             = 3,   //11 GC標(biāo)記
         biased_lock_pattern      = 5    //101 偏向鎖


(, 下載次數(shù): 158)

利用JOL工具跟蹤鎖標(biāo)記變化

偏向鎖

/***StringBuffer內(nèi)部同步***/
public synchronized int length() {
   return count;
}
//System.out.println 無意識的使用鎖
public void println(String x) {
  synchronized (this) {
     print(x); newLine();
  }
}
偏向鎖延遲偏向

//關(guān)閉延遲開啟偏向鎖
-XX:BiasedLockingStartupDelay=0
//禁止偏向鎖
-XX:-UseBiasedLocking
//啟用偏向鎖
@Slf4j
public class LockEscalationDemo{

    public static void main(String[] args) throws InterruptedException {
        log.debug(ClassLayout.parseInstance(new Object()).toPrintable());
        Thread.sleep(4000);
        log.debug(ClassLayout.parseInstance(new Object()).toPrintable());
    }
}偏向鎖狀態(tài)跟蹤

public class LockEscalationDemo {
    public static void main(String[] args) throws InterruptedException {
        log.debug(ClassLayout.parseInstance(new Object()).toPrintable());
        //HotSpot 虛擬機(jī)在啟動后有個 4s 的延遲才會對每個新建的對象開啟偏向鎖模式
        Thread.sleep(4000);
        Object obj = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                log.debug(Thread.currentThread().getName()+"開始執(zhí)行。。。\n"
                        +ClassLayout.parseInstance(obj).toPrintable());
                synchronized (obj){
                    log.debug(Thread.currentThread().getName()+"獲取鎖執(zhí)行中。。。\n"
                            +ClassLayout.parseInstance(obj).toPrintable());
                }
                log.debug(Thread.currentThread().getName()+"釋放鎖。。。\n"
                        +ClassLayout.parseInstance(obj).toPrintable());
            }
        },"thread1").start();
        
        Thread.sleep(5000);
        log.debug(ClassLayout.parseInstance(obj).toPrintable());
   }
}偏向鎖撤銷場景(升級或釋放)

調(diào)用對象HashCode

調(diào)用鎖對象的obj.hashCode()或System.identityHashCode(obj)方法會導(dǎo)致該對象的偏向鎖被撤銷
當(dāng)對象處于可偏向(也就是線程ID為0)和已偏向的狀態(tài)下,調(diào)用HashCode計算將會使對象再也無法偏向:
調(diào)用wait/notify
輕量級鎖

輕量級鎖跟蹤

public class LockEscalationDemo {
    public static void main(String[] args) throws InterruptedException {

        log.debug(ClassLayout.parseInstance(new Object()).toPrintable());
        //HotSpot 虛擬機(jī)在啟動后有個 4s 的延遲才會對每個新建的對象開啟偏向鎖模式
        Thread.sleep(4000);
        Object obj = new Object();
        // 思考: 如果對象調(diào)用了hashCode,還會開啟偏向鎖模式嗎
        obj.hashCode();
       //log.debug(ClassLayout.parseInstance(obj).toPrintable());

        new Thread(new Runnable() {
            @Override
            public void run() {
                log.debug(Thread.currentThread().getName()+"開始執(zhí)行。。。\n"
                        +ClassLayout.parseInstance(obj).toPrintable());
                synchronized (obj){
                    log.debug(Thread.currentThread().getName()+"獲取鎖執(zhí)行中。。。\n"
                            +ClassLayout.parseInstance(obj).toPrintable());
                }
                log.debug(Thread.currentThread().getName()+"釋放鎖。。。\n"
                        +ClassLayout.parseInstance(obj).toPrintable());
            }
        },"thread1").start();
        
        Thread.sleep(5000);
        log.debug(ClassLayout.parseInstance(obj).toPrintable());
   }
}鎖升級場景:TEST

偏向鎖升級為輕量級鎖

輕量級鎖升級(膨脹)為重量級鎖

總結(jié):鎖對象狀態(tài)轉(zhuǎn)換



(, 下載次數(shù): 168)

synchronize(一)梳理

深入理解synchronize

synchronize的使用

鎖升級的原理分析

輕量級鎖源碼分析



(, 下載次數(shù): 156)

重量級鎖源碼分析——Synchronized重量級鎖加鎖解鎖執(zhí)行邏輯



(, 下載次數(shù): 149)

synchronize進(jìn)階篇——synchronize鎖的優(yōu)化

針對偏向鎖的優(yōu)化

批量重偏向(bulk rebias)和批量撤銷(bulk revoke)機(jī)制

原理

每個class對象會有一個對應(yīng)的epoch字段,每個處于偏向鎖狀態(tài)對象的Mark Word中也有該字段,其初始值為創(chuàng)建該對象時class中的epoch的值。每次發(fā)生批量重偏向時,就將該值+1,同時遍歷JVM中所有線程的棧,找到該class所有正處于加鎖狀態(tài)的偏向鎖,將其epoch字段改為新值。下次獲得鎖時,發(fā)現(xiàn)當(dāng)前對象的epoch值和class的epoch不相等,那就算當(dāng)前已經(jīng)偏向了其他線程,也不會執(zhí)行撤銷操作,而是直接通過CAS操作將其Mark Word的Thread Id 改成當(dāng)前線程Id。

應(yīng)用場景

JVM參數(shù)設(shè)置

intx BiasedLockingBulkRebiasThreshold   = 20   //默認(rèn)偏向鎖批量重偏向閾值
intx BiasedLockingBulkRevokeThreshold   = 40   //默認(rèn)偏向鎖批量撤銷閾值測試:批量重偏向

@Slf4j
public class BiasedLockingTest {
    //延時產(chǎn)生可偏向?qū)ο?br />     Thread.sleep(5000);
    // 創(chuàng)建一個list,來存放鎖對象
    List<Object> list = new ArrayList<>();
   
    // 線程1
    new Thread(() -> {
        for (int i = 0; i < 50; i++) {
            // 新建鎖對象
            Object lock = new Object();
            synchronized (lock) {
                list.add(lock);
            }
        }
        try {
            //為了防止JVM線程復(fù)用,在創(chuàng)建完對象后,保持線程thead1狀態(tài)為存活
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "thead1").start();
   
    //睡眠3s鐘保證線程thead1創(chuàng)建對象完成
    Thread.sleep(3000);
    log.debug("打印thead1,list中第20個對象的對象頭:");
    log.debug((ClassLayout.parseInstance(list.get(19)).toPrintable()));
   
    // 線程2
    new Thread(() -> {
        for (int i = 0; i < 40; i++) {
            Object obj = list.get(i);
            synchronized (obj) {
                if(i>=15&&i<=21||i>=38){
                    log.debug("thread2-第" + (i + 1) + "次加鎖執(zhí)行中\(zhòng)t"+
                            ClassLayout.parseInstance(obj).toPrintable());
                }
            }
            if(i==17||i==19){
                log.debug("thread2-第" + (i + 1) + "次釋放鎖\t"+
                        ClassLayout.parseInstance(obj).toPrintable());
            }
        }
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "thead2").start();

    LockSupport.park();
}測試:批量撤銷

@Slf4j
public class BiasedLockingTest {
    public static void main(String[] args) throws  InterruptedException {
        //延時產(chǎn)生可偏向?qū)ο?br />         Thread.sleep(5000);
        // 創(chuàng)建一個list,來存放鎖對象
        List<Object> list = new ArrayList<>();
        
        // 線程1
        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                // 新建鎖對象
                Object lock = new Object();
                synchronized (lock) {
                    list.add(lock);
                }
            }
            try {
                //為了防止JVM線程復(fù)用,在創(chuàng)建完對象后,保持線程thead1狀態(tài)為存活
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thead1").start();

        //睡眠3s鐘保證線程thead1創(chuàng)建對象完成
        Thread.sleep(3000);
        log.debug("打印thead1,list中第20個對象的對象頭:");
        log.debug((ClassLayout.parseInstance(list.get(19)).toPrintable()));
        
        // 線程2
        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                Object obj = list.get(i);
                synchronized (obj) {
                    if(i>=15&&i<=21||i>=38){
                        log.debug("thread2-第" + (i + 1) + "次加鎖執(zhí)行中\(zhòng)t"+
                                ClassLayout.parseInstance(obj).toPrintable());
                    }
                }
                if(i==17||i==19){
                    log.debug("thread2-第" + (i + 1) + "次釋放鎖\t"+
                            ClassLayout.parseInstance(obj).toPrintable());
                }
            }
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thead2").start();


        Thread.sleep(3000);

        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                Object lock =list.get(i);
                if(i>=17&&i<=21||i>=35&&i<=41){
                    log.debug("thread3-第" + (i + 1) + "次準(zhǔn)備加鎖\t"+
                            ClassLayout.parseInstance(lock).toPrintable());
                }
                synchronized (lock){
                    if(i>=17&&i<=21||i>=35&&i<=41){
                        log.debug("thread3-第" + (i + 1) + "次加鎖執(zhí)行中\(zhòng)t"+
                                ClassLayout.parseInstance(lock).toPrintable());
                    }
                }
            }
        },"thread3").start();

        Thread.sleep(3000);
        log.debug("查看新創(chuàng)建的對象");
        log.debug((ClassLayout.parseInstance(new Object()).toPrintable()));

        LockSupport.park();
}小結(jié)

針對重量級鎖的優(yōu)化

自旋優(yōu)化——(1.6之后有了自適應(yīng)自旋)

鎖粗化

StringBuffer buffer = new StringBuffer();
/**
* 鎖粗化
*/
public void append(){
    buffer.append("aaa").append(" bbb").append(" ccc");
}鎖消除

public class LockEliminationTest {
    /**
     * 鎖消除
     * -XX:+EliminateLocks 開啟鎖消除(jdk8默認(rèn)開啟)
     * -XX:-EliminateLocks 關(guān)閉鎖消除
     * @param str1
     * @param str2
     */
    public void append(String str1, String str2) {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(str1).append(str2);
    }

    public static void main(String[] args) throws InterruptedException {
        LockEliminationTest demo = new LockEliminationTest();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            demo.append("aaa", "bbb");
        }
        long end = System.currentTimeMillis();
        System.out.println("執(zhí)行時間:" + (end - start) + " ms");
    }
}
逃逸分析

方法逃逸(對象逃出當(dāng)前方法)

線程逃逸((對象逃出當(dāng)前線程)

逃逸分析使用/作用

使用逃逸分析,編譯器可以對代碼做如下優(yōu)化
JVM參數(shù)指定是否開啟逃逸分析

-XX:+DoEscapeAnalysis  //表示開啟逃逸分析 (jdk1.8默認(rèn)開啟)
-XX:-DoEscapeAnalysis //表示關(guān)閉逃逸分析。
-XX:+EliminateAllocations   //開啟標(biāo)量替換(默認(rèn)打開)
作者: 西路    時間: 2023-4-8 11:32
java中的三種鎖,偏向鎖,輕量級鎖,重量級鎖其實也有很多值得探究的地方,引入偏向鎖是為了在無多線程競爭的情況下盡量減少不必要的輕量級鎖執(zhí)行路徑,因為輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令,代價就是一旦出現(xiàn)多線程競爭的情況就必須撤銷偏向鎖。
Java對象頭的長度

Java的對象頭通常由兩個部分組成,一個是Mark Word存儲對象的hashCode或者鎖信息,另一個是Class Metadata Address用于存儲對象類型數(shù)據(jù)的指針,如果對象是數(shù)組,還會有一個部分存儲的是數(shù)據(jù)的長度


(, 下載次數(shù): 157)

對象頭中Mark Word布局

偏向鎖和輕量級鎖是在Java1.6中引入的,并且規(guī)定鎖只可以升級而不可以降級,這就意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種策略是為了提高獲得鎖和釋放鎖的效率。下面著重寫一下偏向鎖和輕量級鎖的原理。


(, 下載次數(shù): 157)

偏向鎖

偏向鎖的來源是因為Hotsopt的作者研究發(fā)現(xiàn)大多數(shù)情況下,鎖不僅不存在多線程競爭,而且總是由統(tǒng)一線程多次獲得,而線程的阻塞和喚醒需要CPU從用戶態(tài)轉(zhuǎn)為核心態(tài),頻繁的阻塞和喚醒對CPU來說是一件負(fù)擔(dān)很重的工作,為了讓線程獲得鎖的代駕更低而引入了偏向鎖。偏向鎖獲得鎖的過程分為以下幾步:
1)初始時對象的Mark Word位為1,表示對象處于可偏向的狀態(tài),并且ThreadId為0,這是該對象是biasable&unbiased狀態(tài),可以加上偏向鎖進(jìn)入2)。如果一個線程試圖鎖住biasable&biased并且ThreadID不等于自己ID的時候,由于鎖競爭應(yīng)該直接進(jìn)入4)撤銷偏向鎖。
2)線程嘗試用CAS將自己的ThreadID放置到Mark Word中相應(yīng)的位置,如果CAS操作成功進(jìn)入到3),否則進(jìn)入4)
3)進(jìn)入到這一步代表當(dāng)前沒有鎖競爭,Object繼續(xù)保持biasable狀態(tài),但此時ThreadID已經(jīng)不為0了,對象處于biasable&biased狀態(tài)
4)當(dāng)線程執(zhí)行CAS失敗,表示另一個線程當(dāng)前正在競爭該對象上的鎖。當(dāng)?shù)竭_(dá)全局安全點時(cpu沒有正在執(zhí)行的字節(jié))獲得偏向鎖的線程將被掛起,撤銷偏向(偏向位置0),如果這個線程已經(jīng)死了,則把對象恢復(fù)到未鎖定狀態(tài)(標(biāo)志位改為01),如果線程還活著,則把偏向鎖置0,變成輕量級鎖(標(biāo)志位改為00),釋放被阻塞的線程,進(jìn)入到輕量級鎖的執(zhí)行路徑中,同時被撤銷偏向鎖的線程繼續(xù)往下執(zhí)行。
5)運行同步代碼塊
參考文章:http://www.cnblogs.com/javaminer/p/3892288.html?utm_source=tuicool&utm_medium=referral
輕量級鎖

如果說偏向鎖是只允許一個線程獲得鎖,那么輕量級鎖就是允許多個線程獲得鎖,但是只允許他們順序拿鎖,不允許出現(xiàn)競爭,也就是拿鎖失敗的情況,輕量級鎖的步驟如下:
1)線程1在執(zhí)行同步代碼塊之前,JVM會先在當(dāng)前線程的棧幀中創(chuàng)建一個空間用來存儲鎖記錄,然后再把對象頭中的Mark Word復(fù)制到該鎖記錄中,官方稱之為Displaced Mark Word。然后線程嘗試使用CAS將對象頭中的Mark Word 替換為指向鎖記錄的指針。如果成功,則獲得鎖,進(jìn)入步驟3)。如果失敗執(zhí)行步驟2)
2)線程自旋,自旋成功則獲得鎖,進(jìn)入步驟3)。自旋失敗,則膨脹成為重量級鎖,并把鎖標(biāo)志位變?yōu)?0,線程阻塞進(jìn)入步驟3)
3)鎖的持有線程執(zhí)行同步代碼,執(zhí)行完CAS替換Mark Word成功釋放鎖,如果CAS成功則流程結(jié)束,CAS失敗執(zhí)行步驟4)
4)CAS執(zhí)行失敗說明期間有線程嘗試獲得鎖并自旋失敗,輕量級鎖升級為了重量級鎖,此時釋放鎖之后,還要喚醒等待的線程
好了 希望對你學(xué)習(xí)Java上有一點幫助!關(guān)注個人公眾號【終端研發(fā)部】
另外,同學(xué)們有什么具體的困惑,歡迎在留言區(qū)積極探討




歡迎光臨 愛鋒貝 (http://m.7gfy2te7.cn/) Powered by Discuz! X3.4