深讀源碼-java同步系列之ReentrantLock與synchronized對比分析

問題

(1)ReentrantLock有哪些優點?

(2)ReentrantLock有哪些缺點?

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

簡介

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

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

既然有了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)。

到底是不是這樣呢?

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

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.IntStream;

/**
 * @ClassName ReentrantLockVsSynchronizedTest
 * @Author suidd
 * @Description ReentrantLock與synchronized對比分析
 * @Date 10:35 2020/5/24
 * @Version 1.0
 **/
public class ReentrantLockVsSynchronizedTest {
    public static AtomicInteger a = new AtomicInteger(0);
    public static LongAdder b = new LongAdder();
    public static int c;
    public static int d;
    public static int e;

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

    /**
     * @return
     * @Author suidd
     * @Description 測試
     * @Date 10:58 2020/5/24
     * @Param
     * @Version 1.0
     **/
    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);
    }

    /**
     * @return
     * @Author suidd
     * @Description 測試AtomicInteger
     * @Date 10:50 2020/5/24
     * @Param
     * @Version 1.0
     **/
    public static void testAtomicInteger(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        IntStream.range(0, threadCount)
                .forEach(i -> new Thread(() -> {
                    IntStream.range(0, loopCount)
                            .forEach(j -> a.incrementAndGet());
                    countDownLatch.countDown();
                }).start());

        countDownLatch.await();

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

    /**
     * @return
     * @Author suidd
     * @Description 測試LongAdder
     * @Date 10:51 2020/5/24
     * @Param
     * @Version 1.0
     **/
    public static void testLongAdder(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        IntStream.range(0, threadCount)
                .forEach(i -> new Thread(() -> {
                    IntStream.range(0, loopCount)
                            .forEach(j -> b.increment());
                    countDownLatch.countDown();
                }).start());

        countDownLatch.await();

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

    /**
     * @return
     * @Author suidd
     * @Description 測試ReentrantLock公平鎖
     * @Date 10:52 2020/5/24
     * @Param
     * @Version 1.0
     **/
    public static void testReentrantLockFair(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        IntStream.range(0, threadCount)
                .forEach(i -> new Thread(() -> {
                    IntStream.range(0, loopCount)
                            .forEach(j -> {
                                fairLock.lock();
                                c++;
                                fairLock.unlock();
                            });
                    countDownLatch.countDown();
                }).start());

        countDownLatch.await();

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

    /**
     * @return
     * @Author suidd
     * @Description 測試ReentrantLock非公平鎖
     * @Date 10:55 2020/5/24
     * @Param
     * @Version 1.0
     **/
    public static void testReentrantLockUnfair(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        IntStream.range(0, threadCount)
                .forEach(i -> new Thread(() -> {
                    IntStream.range(0, loopCount)
                            .forEach(j -> {
                                unfairLock.lock();
                                d++;
                                unfairLock.unlock();
                            });
                    countDownLatch.countDown();
                }).start());

        countDownLatch.await();

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

    /**
     * @return
     * @Author suidd
     * @Description 測試Synchronized
     * @Date 10:57 2020/5/24
     * @Param
     * @Version 1.0
     **/
    public static void testSynchronized(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        IntStream.range(0, threadCount)
                .forEach(i -> new Thread(() -> {
                    IntStream.range(0, loopCount)
                            .forEach(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的效率低一些。


本人8核8G,測試出來的數據是 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魔法類之Unsafe解析》

  2. 《深讀源碼-java同步系列之JMM(Java Memory Model)》

  3. 《深讀源碼-java同步系列之volatile解析》

  4. 《深讀源碼-java同步系列之synchronized解析》

  5. 《深讀源碼-java同步系列之自己手寫一個鎖Lock》

  6. 《深讀源碼-java同步系列之AQS簡介》

  7. 《深讀源碼-java同步系列之ReentrantLock源碼解析(一)——公平鎖、非公平鎖》

  8. 《深讀源碼-java同步系列之ReentrantLock源碼解析(二)——條件鎖》


參考鏈接:https://www.cnblogs.com/tong-yuan/p/11001615.html 

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