公平鎖與非公平鎖的對比

掃描下方二維碼或者微信搜索公衆號菜鳥飛呀飛,便可關注微信公衆號,閱讀更多Spring源碼分析Java併發編程文章。java

微信公衆號

1. 問題

  • 在上一篇文章中結合源碼介紹了公平鎖和非公平鎖的實現【文章連接】。這一篇文章將從公平性和性能方面對比一下二者。
  • 在閱讀本文以前,能夠先思考一下下面兩個問題。
  • 1. 非公平鎖必定不公平嗎?
  • 2. 公平鎖與非公平鎖的性能誰更好?

2. 對比

  • 主要從公平性和性能這兩個方面來對比一下公平鎖和非公平鎖。

2.1 公平性

  • 在上一篇文章的總結處,提到了公平鎖和非公平鎖是從線程獲取鎖所等待的時間來區分二者的公平性。公平鎖中,多個線程搶鎖時,獲取到鎖的線程必定是同步隊列中等待時間最長的線程。而非公平鎖中,多個線程搶鎖時,獲取鎖的線程不必定是同步隊列中等待時間最長的線程,有多是同步隊列以外的線程先搶到鎖。
  • 對於公平鎖,線程獲取鎖的過程能夠用以下示意圖表示(圖片來源於公衆號:Mr羽墨青衫,文章連接:深刻剖析Java重入鎖ReentrantLock的實現原理)。

公平鎖搶鎖示意圖

  • 從圖中能夠發現,當持有鎖的線程T1釋放鎖之後,會喚醒同步隊列中的T2線程,只要同步隊列中有線程在等待獲取鎖,那麼其餘剛進來想要獲取鎖的人,就不能插隊,包括T1本身還想要獲取鎖,也須要去排隊,這樣就保證了讓等待時間最長的線程獲取到鎖,即保證了公平性。編程

  • 對於非公平鎖,線程獲取鎖的示意圖能夠用以下示意圖表示。(圖片來源於公衆號:Mr羽墨青衫,文章連接:深刻剖析Java重入鎖ReentrantLock的實現原理)。設計模式

非公平鎖搶鎖示意圖

  • 從圖中能夠發現,當持有鎖的線程T1釋放鎖之後,會喚醒同步隊列中的T2線程,此時即便同步隊列中有線程在排隊,從外面剛進來的線程想要獲取鎖,此時是能夠直接去爭搶鎖的,包括線程T1本身,這個時候就是T二、T一、外面剛進來的線程一塊兒去搶鎖,雖然此時T2線程等待的時間最長,可是不能保證T2必定搶到鎖,因此此時是不公平的。
  • 文章開頭提到了一個問題,非公平鎖必定不公平嗎?答案是不必定,對於剛剛上面圖中展現的那種狀況,此時非公平鎖是不公平的。可是存在一種特殊狀況,能夠參考以下示意圖,如當T1線程釋放鎖之後,AQS同步隊列外部沒有線程來爭搶鎖,T1線程在釋放鎖之後,本身也不須要獲取鎖了,此時T1由於喚醒的是T2,如今是有T2一個線程來搶鎖,因此此時能獲取到鎖的線程必定是T2,這種狀況下,非公平鎖又是公平的了,由於此時即便同步隊列中有T三、T四、...、Tn線程,可是T2等待的時間最長,因此是T2獲取到鎖(AQS的同步隊列遵循的原則是FIFO)。

非公平鎖搶鎖特殊場景

  • 從非公平鎖的示意圖中,咱們也能夠發現,若是線程一旦獲取鎖失敗進入到AQS同步隊列後,就會一直排隊,直到其餘獲取到鎖的線程喚醒它或者它被其餘線程中斷,纔會出隊。即:一朝排隊,永遠排隊。

性能

  • 公平鎖和非公平鎖的性能是不同的,非公平鎖的性能會優於公平鎖。爲何呢?由於公平鎖在獲取鎖時,永遠是等待時間最長的線程獲取到鎖,這樣當線程T1釋放鎖之後,若是還想繼續再獲取鎖,它也得去同步隊列尾部排隊,這樣就會頻繁的發生線程的上下文切換,當線程越多,對CPU的損耗就會越嚴重。
  • 非公平鎖性能雖然優於公平鎖,可是會存在致使線程飢餓的狀況。在最壞的狀況下,可能存在某個線程一直獲取不到鎖。不過相比性能而言,飢餓問題能夠暫時忽略,這可能就是ReentrantLock默認建立非公平鎖的緣由之一了。
  • 下面以一個demo爲例,對比了一下公平鎖與非公平鎖的性能。
public class Demo {

    // 公平鎖
    private static Lock fairLock = new ReentrantLock(true);

    // 非公平鎖
    private static Lock nonFairLock = new ReentrantLock(false);

    // 計數器
    private static int fairCount = 0;

    // 計數器
    private static int nonFairCount = 0;

    public static void main(String[] args) throws InterruptedException {
        System.out.println("公平鎖耗時: " + testFairLock(10));
        System.out.println("非公平鎖耗時: " + testNonFairLock(10));
        System.out.println("公平鎖累加結果: " + fairCount);
        System.out.println("非公平鎖累加結果: " + nonFairCount);
    }

    public static long testFairLock(int threadNum) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(threadNum);
        // 建立threadNum個線程,讓其以公平鎖的方式,對fairCount進行自增操做
        List<Thread> fairList = new ArrayList<>();
        for (int i = 0; i < threadNum; i++) {
            fairList.add(new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    fairLock.lock();
                    fairCount++;
                    fairLock.unlock();
                }
                countDownLatch.countDown();
            }));
        }

        long startTime = System.currentTimeMillis();
        for (Thread thread : fairList) {
            thread.start();
        }
        // 讓全部線程執行完
        countDownLatch.await();
        long endTime = System.currentTimeMillis();

        return endTime - startTime;
    }

    public static long testNonFairLock(int threadNum) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(threadNum);
        // 建立threadNum個線程,讓其以非公平鎖的方式,對nonFairCountCount進行自增操做
        List<Thread> nonFairList = new ArrayList<>();
        for (int i = 0; i < threadNum; i++) {
            nonFairList.add(new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    nonFairLock.lock();
                    nonFairCount++;
                    nonFairLock.unlock();
                }
                countDownLatch.countDown();
            }));
        }
        long startTime = System.currentTimeMillis();
        for (Thread thread : nonFairList) {
            thread.start();
        }
        // 讓全部線程執行完
        countDownLatch.await();
        long endTime = System.currentTimeMillis();

        return endTime - startTime;
    }
}
複製代碼
  • 上面的Demo中,建立了threadNum個線程,而後讓這threadNum個線程併發的對變量進行累加10000次的操做,分別用公平鎖和非公平鎖來保證線程安全,最後分別統計出公平鎖和非公平鎖的耗時結果。
  • threadNum = 10時,重複三次測試的結果以下。
次數 公平鎖 非公平鎖
1 618ms 22ms
2 544ms 20ms
3 569ms 15ms
  • threadNum = 20時,重複三次測試的結果以下。
次數 公平鎖 非公平鎖
1 1208ms 25ms
2 1146ms 26ms
3 1215ms 19ms
  • threadNum = 30時,重複三次測試的結果以下。
次數 公平鎖 非公平鎖
1 1595ms 28ms
2 1543ms 31ms
3 1601ms 31ms
  • 測試環境:macOS 10.14.6,處理器:2.9GHZ,Intel Core i7,內存:16G 2133MHz
  • 從上面的測試結果能夠發現,非公平鎖的耗時遠遠小於公平鎖的耗時,這說明非公平鎖在併發狀況下,性能更好,吞吐量更大。當線程數越多時,差別越明顯。

相關推薦

微信公衆號
相關文章
相關標籤/搜索