大廠面試題:ReentrantLock 與 synchronized異同點對比

JavaBuild發表於2024-04-22

寫在開頭

在過去的博文中我們學習了ReentrantLocksynchronized這兩種Java併發使用頻率最高的同步鎖,在很多大廠面試題中有個經典考題:

ReentrantLock 與 synchronized異同點對比!

今天我們針對這一考題來做一個儘可能全面的總結哈。

ReentrantLock 與 synchronized

ReentrantLock是一種獨佔式的可重入鎖,位於java.util.concurrent.locks中,是Lock介面的預設實現類,底部的同步特性基於AQS實現,和synchronized關鍵字類似,但更靈活、功能更強大、也是目前實戰中使用頻率非常高的同步類。

synchronized 依賴於 JVM 而 ReentrantLock 依賴於 API

synchronized 是依賴於 JVM 實現的,虛擬機器團隊在 JDK1.6 為 synchronized 關鍵字進行了很多最佳化,但是這些最佳化都是在虛擬機器層面實現的,並沒有直接暴露給我們。

ReentrantLock 是 JDK 層面實現的(也就是 API 層面,需要 lock() 和 unlock() 方法配合 try/finally 語句塊來完成),ReentrantLock 比 synchronized 增加了一些高階功能。

區別羅列

  1. ReentrantLock 是一個類,而 synchronized 是 Java 中的關鍵字;
  2. ReentrantLock 必須手動釋放鎖。通常需要在 finally 塊中呼叫 unlock 方法以確保鎖被正確釋放;
  3. ReentrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的執行緒先獲得鎖;
  4. synchronized 會自動釋放鎖,當同步塊執行完畢時,由 JVM 自動釋放,不需要手動操作;
  5. ReentrantLock 可以實現多路選擇通知(可以繫結多個 Condition),而 synchronized 只能透過 wait 和 notify/notifyAll 方法喚醒一個執行緒或者喚醒全部執行緒(單路通知);
  6. ReentrantLock提供了一種能夠中斷等待鎖的執行緒的機制,透過 lock.lockInterruptibly() 來實現這個機制。也就是說正在等待的執行緒可以選擇放棄等待,改為處理其他事情。而synchronized不具備這種特點。
  7. ReentrantLock: 通常提供更好的效能,特別是在高競爭環境下;
  8. synchronized: 在某些情況下,效能可能稍差一些,但隨著 JDK 版本的升級,效能差距已經不大了。

【注】:Condition是 JDK1.5 之後才有的,它具有很好的靈活性,比如可以實現多路通知功能也就是在一個Lock物件中可以建立多個Condition例項(即物件監視器),執行緒物件可以註冊在指定的Condition中,從而可以有選擇性的進行執行緒通知,在排程執行緒上更加靈活,我們在後面的學習中會耽誤聊一聊它!

效能對比

雖然說JDK1.6後synchronized的效能有很大的提升了,但是相比較而言,兩者之間仍然存在效能差別,我們透過一個小demo來測試一下。

public class Test {

    private static final int NUM_THREADS = 10;
    private static final int NUM_INCREMENTS = 1000000;

    private int count1 = 0;
    private int count2 = 0;

    private final ReentrantLock lock = new ReentrantLock();
    private final Object syncLock = new Object();

    public void increment1() {
        lock.lock();
        try {
            count1++;
        } finally {
            lock.unlock();
        }
    }

    public void increment2() {
        synchronized (syncLock) {
            count2++;
        }
    }

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

        Test test = new Test();

        // ReentrantLock效能測試
        long startTime = System.nanoTime();
        Thread[] threads = new Thread[NUM_THREADS];
        for (int i = 0; i < NUM_THREADS; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < NUM_INCREMENTS; j++) {
                    test.increment1();
                }
            });
            threads[i].start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
        long endTime = System.nanoTime();
        System.out.println("ReentrantLock完成時間: " + (endTime - startTime) + " ns");

        // synchronized效能測試
        startTime = System.nanoTime();
        for (int i = 0; i < NUM_THREADS; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < NUM_INCREMENTS; j++) {
                    test.increment2();
                }
            });
            threads[i].start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
        endTime = System.nanoTime();
        System.out.println("synchronized完成時間: " + (endTime - startTime) + " ns");
    }
}

我們採用10個執行緒,每個執行緒做加1000000操作,執行時間對比如下:

//1000000萬資料量時
ReentrantLock完成時間: 272427700 ns
synchronized完成時間: 675759100 ns
//10000資料量時
ReentrantLock完成時間: 52207600 ns
synchronized完成時間: 11291600 ns

很明顯在資料量比較大的時候,競爭激烈時,ReentrantLock的效能要比synchronized好很多,但在資料量較低的情況下,會呈現出不同的結果。

結尾彩蛋

如果本篇部落格對您有一定的幫助,大家記得留言+點贊+收藏呀。原創不易,轉載請聯絡Build哥!

image

如果您想與Build哥的關係更近一步,還可以關注“JavaBuild888”,在這裡除了看到《Java成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!

image

相關文章