死磕 java同步系列之ReentrantLock VS synchronized——結果可能跟你想的不同

問題

(1)ReentrantLock有哪些優勢?java

(2)ReentrantLock有哪些缺點?c++

(3)ReentrantLock是否能夠徹底替代synchronized?git

簡介

synchronized是Java原生提供的用於在多線程環境中保證同步的關鍵字,底層是經過修改對象頭中的MarkWord來實現的。多線程

ReentrantLock是Java語言層面提供的用於在多線程環境中保證同步的類,底層是經過原子更新狀態變量state來實現的。oop

既然有了synchronized的關鍵字來保證同步了,爲何還要實現一個ReentrantLock類呢?它們之間有什麼異同呢?性能

ReentrantLock VS synchronized

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

功能 ReentrantLock synchronized
可重入 支持 支持
非公平 支持(默認) 支持
加鎖/解鎖方式 須要手動加鎖、解鎖,通常使用try..finally..保證鎖可以釋放 手動加鎖,無需刻意解鎖
按key鎖 不支持,好比按用戶id加鎖 支持,synchronized加鎖時須要傳入一個對象
公平鎖 支持,new ReentrantLock(true) 不支持
中斷 支持,lockInterruptibly() 不支持
嘗試加鎖 支持,tryLock() 不支持
超時鎖 支持,tryLock(timeout, unit) 不支持
獲取當前線程獲取鎖的次數 支持,getHoldCount() 不支持
獲取等待的線程 支持,getWaitingThreads() 不支持
檢測是否被當前線程佔有 支持,isHeldByCurrentThread() 不支持
檢測是否被任意線程佔有 支持,isLocked() 不支持
條件鎖 可支持多個條件,condition.await(),condition.signal(),condition.signalAll() 只支持一個,obj.wait(),obj.notify(),obj.notifyAll()

對比測試

在測試以前,咱們先預想一下結果,隨着線程數的不斷增長,ReentrantLock(fair)、ReentrantLock(unfair)、synchronized三者的效率怎樣呢?優化

我猜想應該是ReentrantLock(unfair)> synchronized > ReentrantLock(fair)。spa

究竟是不是這樣呢?線程

直接上測試代碼:(爲了全面對比,彤哥這裏把AtomicInteger和LongAdder也拿來一塊兒對比了)

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));
    }

}
複製代碼

運行這段代碼,你會發現結果大大出乎意料,真的是不測不知道,一測嚇一跳,運行後發現如下規律:

隨着線程數的不斷增長,synchronized的效率居然比ReentrantLock非公平模式要高!

彤哥的電腦上大概是高3倍左右,個人運行環境是4核8G,java版本是8,請你們必定要在本身電腦上運行一下,而且最好能給我反饋一下。

彤哥又使用Java7及如下的版本運行了,發如今Java7及如下版本中synchronized的效率確實比ReentrantLock的效率低一些。

總結

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

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

(3)ReentrantLock的功能很是豐富,解決了不少synchronized的侷限性;

(4)至於在非公平模式下,ReentrantLock與synchronized的效率孰高孰低,彤哥給出的結論是隨着Java版本的不斷升級,synchronized的效率只會愈來愈高;

彩蛋

既然ReentrantLock的功能更豐富,並且效率也不低,咱們是否是能夠放棄使用synchronized了呢?

答:我認爲不是。由於synchronized是Java原生支持的,隨着Java版本的不斷升級,Java團隊也是在不斷優化synchronized,因此我認爲在功能相同的前提下,最好仍是使用原生的synchronized關鍵字來加鎖,這樣咱們就能得到Java版本升級帶來的免費的性能提高的空間。

另外,在Java8的ConcurrentHashMap中已經把ReentrantLock換成了synchronized來分段加鎖了,這也是Java版本不斷升級帶來的免費的synchronized的性能提高。

推薦閱讀

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

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

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

  4. 死磕 java同步系列之本身動手寫一個鎖Lock

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

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

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

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


歡迎關注個人公衆號「彤哥讀源碼」,查看更多源碼系列文章, 與彤哥一塊兒暢遊源碼的海洋。

qrcode
相關文章
相關標籤/搜索