理解synchronized 和lock

鎖是并發(fā)編程中經(jīng)常用到的,本文主要分析下synchronized和lock鎖機(jī)制的區(qū)別。

性能區(qū)別

分兩種場景來比較,競爭不激烈和競爭激烈情況

競爭不激烈

private static final int THREAD_NUM = 10000;

    private static int test = 0;

    private static ThreadPoolExecutor pool =(ThreadPoolExecutor) Executors.newFixedThreadPool(THREAD_NUM);

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {

        long startTime = System.currentTimeMillis();
        for (int i = 0; i < THREAD_NUM; i++) {
            pool.execute(TestReentrantLock::testLock);
            pool.execute(TestReentrantLock::testSynchronized);
        }
        pool.shutdown();
        while(pool.getActiveCount()!=0) {}
        long endTime = System.currentTimeMillis();
        System.out.println(endTime - startTime);
    }

    private static void testLock() {

        try {
            lock.lock();
            test++;
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            lock.unlock();
        }
    }

    private static synchronized void testSynchronized() {

        test++;
    }

上面代碼都是簡單的實(shí)現(xiàn),開啟10000個線程對一個變量++操作,競爭不激烈,結(jié)果顯示都在1000左右,不分上下

競爭激烈

public class TestReentrantLock {


    private static final int THREAD_NUM = 10;

    private static double test = 0;

    private static ThreadPoolExecutor pool = (ThreadPoolExecutor) Executors.newFixedThreadPool(THREAD_NUM);

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {

        long startTime = System.currentTimeMillis();
        for (int i = 0; i < THREAD_NUM; i++) {
            pool.execute(TestReentrantLock::testLock);
            //pool.execute(TestReentrantLock::testSynchronized);
        }
        pool.shutdown();
        while (pool.getActiveCount() != 0) {
        }
        long endTime = System.currentTimeMillis();
        System.out.println(endTime - startTime);
    }

    private static void testLock() {

        for (int i = 0; i < 1000; i++) {
            lock.lock();
            try {
                Thread.sleep(2);
                test++;
            } catch (Exception e) {
                System.out.println(e.getMessage());
            } finally {
                lock.unlock();
            }
        }
    }
    private static void testSynchronized() {

        for (int i = 0; i < 1000; i++) {
            synchronized (lock) {
                try {
                    Thread.sleep(2);
                    test++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

構(gòu)建競爭激烈場景,Synchronized性能和lock的性能也差不多
自從synchronized優(yōu)化之后,總之不管競爭激烈還是不激烈,Synchronized和lock的性能都差不多。

原理介紹

synchronized

JDK1.5中,synchronized是性能低效的。因?yàn)檫@是一個重量級操作,它對性能最大的影響是阻塞的是實(shí)現(xiàn),掛起線程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成,這些操作給系統(tǒng)的并發(fā)性帶來了很大的壓力。到了JDK1.6,對synchronized加入了很多優(yōu)化措施,有自適應(yīng)自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。
要想理解synchronized實(shí)現(xiàn),首先要知道java對象的數(shù)據(jù)結(jié)構(gòu)

java對象數(shù)據(jù)結(jié)構(gòu)

對象在內(nèi)存中存儲的布局可以分為3塊區(qū)域:對象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)和對齊填充(Padding)。
其中對象頭主要包括兩部分:markWord和klass
markWord數(shù)據(jù)結(jié)構(gòu)如下:
hash: 保存對象的哈希碼
age: 保存對象的分代年齡
biased_lock: 偏向鎖標(biāo)識位
lock: 鎖狀態(tài)標(biāo)識位
JavaThread:* 保存持有偏向鎖的線程ID
epoch: 保存偏向時間戳
對象頭的另外一部分是klass類型指針,即對象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過這個指針來確定這個對象是哪個類的實(shí)例。
其中鎖狀態(tài)標(biāo)識位標(biāo)識了重量級鎖、輕量級鎖和偏向鎖狀態(tài),內(nèi)容如下:


無標(biāo)題.png

偏向鎖

偏向鎖,算是競爭鎖資源的第一層緩沖,一般指向第一個訪問鎖線程,如果沒有競爭,對象頭一直處于偏向鎖狀態(tài),當(dāng)只有發(fā)生鎖資源競爭的時候偏向鎖才會撤銷,升級成輕量級鎖,或者無鎖狀態(tài)

偏向鎖獲取過程:

第一步,首先訪問Mark Word中偏向鎖的標(biāo)識是否設(shè)置成1,鎖標(biāo)志位是否為01,確認(rèn)是否是偏向鎖。
第二步,如果為可偏向狀態(tài),則測試線程ID是否指向當(dāng)前線程,如果是,進(jìn)入步驟5,否則進(jìn)入步驟3。
第三步,如果線程ID并未指向當(dāng)前線程,則通過CAS操作競爭鎖。如果競爭成功,則將Mark Word中線程ID設(shè)置為當(dāng)前線程ID,然后執(zhí)行5;如果競爭失敗,執(zhí)行4。
第四步,如果CAS獲取偏向鎖失敗,則表示有競爭。當(dāng)?shù)竭_(dá)全局安全點(diǎn)時獲得偏向鎖的線程被掛起,偏向鎖升級為輕量級鎖,然后被阻塞在安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼。(撤銷偏向鎖的時候會導(dǎo)致stop the word)
第五步,執(zhí)行同步代碼。

偏向鎖釋放過程:

偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖,線程不會主動去釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(diǎn),它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處于被鎖定狀態(tài),撤銷偏向鎖后恢復(fù)到未鎖定(標(biāo)志位為“01”)或輕量級鎖(標(biāo)志位為“00”)的狀態(tài)。

輕量級鎖

輕量級鎖是偏向鎖升級來的,即線程發(fā)生競爭,偏向鎖會升級成輕量級鎖

輕量級鎖獲取過程:

第一步,在線程進(jìn)入同步代碼塊中的時候,虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個名為鎖記錄的空間用于存儲對象的Mark Word。
第二步,把對象的Mark Word復(fù)制到該鎖記錄中。
第三步,虛擬機(jī)將使用CAS操作嘗試將對象的Mark Word中的內(nèi)容更新為指向鎖記錄的指針,成功執(zhí)行步驟4,否則執(zhí)行步驟5。
第四步,更新成功,那么這個線程就擁有了該對象的鎖,并且對象Mark Word的鎖標(biāo)志位設(shè)置為“00”,即表示此對象處于輕量級鎖定狀態(tài)。
第五步,更新失敗,虛擬機(jī)首先會檢查對象的Mark Word是否指向當(dāng)前線程的棧幀,如果是就說明當(dāng)前線程已經(jīng)擁有了這個對象的鎖,那就可以直接進(jìn)入同步塊。否則說明多個線程競爭鎖,輕量級鎖升級為重量級鎖,鎖標(biāo)志的狀態(tài)值變?yōu)椤?0”。這里升級重量級鎖之前,有一個自旋的過程,自旋成功獲取鎖就不需要升級到重量級鎖,失敗升級重量級鎖。

輕量級鎖釋放過程:

輕量級鎖在釋放鎖的時候如果它發(fā)現(xiàn)在它持有鎖的期間有其他線程來嘗試獲取鎖了,即獲取過程中第二步對markword做了修改,兩者比對發(fā)現(xiàn)不一致,則切換到重量鎖。

重量級鎖

Mark Word中存儲的就是指向重量級鎖(互斥量)的指針,后面等待鎖的線程也要進(jìn)入阻塞狀態(tài)。

總結(jié):
synchronized·就是一個鎖升級的過程,偏向鎖->輕量級鎖(自旋)->重量級鎖,不能降級。

Lock實(shí)現(xiàn)

Lock是基于AQS實(shí)現(xiàn)的,可以參考ReentrantLock源碼解析

Lock vs synchronized

而lock的優(yōu)勢在于
1,更自由,不限制于鎖加于類,方法上,可以跨方法持有鎖
2,支持輪詢鎖,定時鎖,可中斷鎖等
synchronized的優(yōu)勢在于
1,用法簡單直接,原語關(guān)鍵字
2,不會產(chǎn)生gc垃圾。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容