溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

死磕 java同步系列之ReentrantLock VS synchronized——結(jié)果可能跟你想

發(fā)布時(shí)間:2020-07-24 11:40:08 來(lái)源:網(wǎng)絡(luò) 閱讀:256 作者:彤哥讀源碼 欄目:編程語(yǔ)言

問(wèn)題

(1)ReentrantLock有哪些優(yōu)點(diǎn)?

(2)ReentrantLock有哪些缺點(diǎn)?

(3)ReentrantLock是否可以完全替代synchronized?

簡(jiǎn)介

synchronized是Java原生提供的用于在多線程環(huán)境中保證同步的關(guān)鍵字,底層是通過(guò)修改對(duì)象頭中的MarkWord來(lái)實(shí)現(xiàn)的。

ReentrantLock是Java語(yǔ)言層面提供的用于在多線程環(huán)境中保證同步的類,底層是通過(guò)原子更新?tīng)顟B(tài)變量state來(lái)實(shí)現(xiàn)的。

既然有了synchronized的關(guān)鍵字來(lái)保證同步了,為什么還要實(shí)現(xiàn)一個(gè)ReentrantLock類呢?它們之間有什么異同呢?

ReentrantLock VS synchronized

直接上表格:(手機(jī)橫屏查看更方便)

功能 ReentrantLock synchronized
可重入 支持 支持
非公平 支持(默認(rèn)) 支持
加鎖/解鎖方式 需要手動(dòng)加鎖、解鎖,一般使用try..finally..保證鎖能夠釋放 手動(dòng)加鎖,無(wú)需刻意解鎖
按key鎖 不支持,比如按用戶id加鎖 支持,synchronized加鎖時(shí)需要傳入一個(gè)對(duì)象
公平鎖 支持,new ReentrantLock(true) 不支持
中斷 支持,lockInterruptibly() 不支持
嘗試加鎖 支持,tryLock() 不支持
超時(shí)鎖 支持,tryLock(timeout, unit) 不支持
獲取當(dāng)前線程獲取鎖的次數(shù) 支持,getHoldCount() 不支持
獲取等待的線程 支持,getWaitingThreads() 不支持
檢測(cè)是否被當(dāng)前線程占有 支持,isHeldByCurrentThread() 不支持
檢測(cè)是否被任意線程占有 支持,isLocked() 不支持
條件鎖 可支持多個(gè)條件,condition.await(),condition.signal(),condition.signalAll() 只支持一個(gè),obj.wait(),obj.notify(),obj.notifyAll()

對(duì)比測(cè)試

在測(cè)試之前,我們先預(yù)想一下結(jié)果,隨著線程數(shù)的不斷增加,ReentrantLock(fair)、ReentrantLock(unfair)、synchronized三者的效率怎樣呢?

我猜測(cè)應(yīng)該是ReentrantLock(unfair)> synchronized > ReentrantLock(fair)。

到底是不是這樣呢?

直接上測(cè)試代碼:(為了全面對(duì)比,彤哥這里把AtomicInteger和LongAdder也拿來(lái)一起對(duì)比了)

public class ReentrantLockVsSynchronizedTest {
    public static AtomicInteger a = new AtomicInteger(0);
    public static LongAdder b = new LongAdder();
    public static int c = 0;
    public static int d = 0;
    public static int e = 0;

    public static final ReentrantLock fairLock = new ReentrantLock(true);
    public static final ReentrantLock unfairLock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        System.out.println("-------------------------------------");
        testAll(1, 100000);
        System.out.println("-------------------------------------");
        testAll(2, 100000);
        System.out.println("-------------------------------------");
        testAll(4, 100000);
        System.out.println("-------------------------------------");
        testAll(6, 100000);
        System.out.println("-------------------------------------");
        testAll(8, 100000);
        System.out.println("-------------------------------------");
        testAll(10, 100000);
        System.out.println("-------------------------------------");
        testAll(50, 100000);
        System.out.println("-------------------------------------");
        testAll(100, 100000);
        System.out.println("-------------------------------------");
        testAll(200, 100000);
        System.out.println("-------------------------------------");
        testAll(500, 100000);
        System.out.println("-------------------------------------");
//        testAll(1000, 1000000);
        System.out.println("-------------------------------------");
        testAll(500, 10000);
        System.out.println("-------------------------------------");
        testAll(500, 1000);
        System.out.println("-------------------------------------");
        testAll(500, 100);
        System.out.println("-------------------------------------");
        testAll(500, 10);
        System.out.println("-------------------------------------");
        testAll(500, 1);
        System.out.println("-------------------------------------");
    }

    public static void testAll(int threadCount, int loopCount) throws InterruptedException {
        testAtomicInteger(threadCount, loopCount);
        testLongAdder(threadCount, loopCount);
        testSynchronized(threadCount, loopCount);
        testReentrantLockUnfair(threadCount, loopCount);
//        testReentrantLockFair(threadCount, loopCount);
    }

    public static void testAtomicInteger(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                for (int j = 0; j < loopCount; j++) {
                    a.incrementAndGet();
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testAtomicInteger: result=" + a.get() + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

    public static void testLongAdder(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                for (int j = 0; j < loopCount; j++) {
                    b.increment();
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testLongAdder: result=" + b.sum() + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

    public static void testReentrantLockFair(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                for (int j = 0; j < loopCount; j++) {
                    fairLock.lock();
                    // 消除try的性能影響
//                    try {
                        c++;
//                    } finally {
                        fairLock.unlock();
//                    }
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testReentrantLockFair: result=" + c + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

    public static void testReentrantLockUnfair(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                for (int j = 0; j < loopCount; j++) {
                    unfairLock.lock();
                    // 消除try的性能影響
//                    try {
                        d++;
//                    } finally {
                        unfairLock.unlock();
//                    }
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testReentrantLockUnfair: result=" + d + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

    public static void testSynchronized(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                for (int j = 0; j < loopCount; j++) {
                    synchronized (ReentrantLockVsSynchronizedTest.class) {
                        e++;
                    }
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testSynchronized: result=" + e + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

}

運(yùn)行這段代碼,你會(huì)發(fā)現(xiàn)結(jié)果大大出乎意料,真的是不測(cè)不知道,一測(cè)嚇一跳,運(yùn)行后發(fā)現(xiàn)以下規(guī)律:

隨著線程數(shù)的不斷增加,synchronized的效率竟然比ReentrantLock非公平模式要高!

彤哥的電腦上大概是高3倍左右,我的運(yùn)行環(huán)境是4核8G,java版本是8,請(qǐng)大家一定要在自己電腦上運(yùn)行一下,并且最好能給我反饋一下。

彤哥又使用Java7及以下的版本運(yùn)行了,發(fā)現(xiàn)在Java7及以下版本中synchronized的效率確實(shí)比ReentrantLock的效率低一些。

總結(jié)

(1)synchronized是Java原生關(guān)鍵字鎖;

(2)ReentrantLock是Java語(yǔ)言層面提供的鎖;

(3)ReentrantLock的功能非常豐富,解決了很多synchronized的局限性;

(4)至于在非公平模式下,ReentrantLock與synchronized的效率孰高孰低,彤哥給出的結(jié)論是隨著Java版本的不斷升級(jí),synchronized的效率只會(huì)越來(lái)越高;

彩蛋

既然ReentrantLock的功能更豐富,而且效率也不低,我們是不是可以放棄使用synchronized了呢?

答:我認(rèn)為不是。因?yàn)閟ynchronized是Java原生支持的,隨著Java版本的不斷升級(jí),Java團(tuán)隊(duì)也是在不斷優(yōu)化synchronized,所以我認(rèn)為在功能相同的前提下,最好還是使用原生的synchronized關(guān)鍵字來(lái)加鎖,這樣我們就能獲得Java版本升級(jí)帶來(lái)的免費(fèi)的性能提升的空間。

另外,在Java8的ConcurrentHashMap中已經(jīng)把ReentrantLock換成了synchronized來(lái)分段加鎖了,這也是Java版本不斷升級(jí)帶來(lái)的免費(fèi)的synchronized的性能提升。

推薦閱讀

  1. 死磕 java同步系列之ReentrantLock源碼解析(二)——條件鎖

  2. 死磕 java同步系列之ReentrantLock源碼解析(一)——公平鎖、非公平鎖

  3. 死磕 java同步系列之AQS起篇

  4. 死磕 java同步系列之自己動(dòng)手寫一個(gè)鎖Lock

  5. 死磕 java魔法類之Unsafe解析

  6. 死磕 java同步系列之JMM(Java Memory Model)

  7. 死磕 java同步系列之volatile解析

  8. 死磕 java同步系列之synchronized解析

歡迎關(guān)注我的公眾號(hào)“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。

死磕 java同步系列之ReentrantLock VS synchronized——結(jié)果可能跟你想

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI