寫在開頭
在過去的博文中我們學習了ReentrantLock 與 synchronized這兩種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 增加了一些高階功能。
區別羅列
- ReentrantLock 是一個類,而 synchronized 是 Java 中的關鍵字;
- ReentrantLock 必須手動釋放鎖。通常需要在 finally 塊中呼叫 unlock 方法以確保鎖被正確釋放;
- ReentrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的執行緒先獲得鎖;
- synchronized 會自動釋放鎖,當同步塊執行完畢時,由 JVM 自動釋放,不需要手動操作;
- ReentrantLock 可以實現多路選擇通知(可以繫結多個 Condition),而 synchronized 只能透過 wait 和 notify/notifyAll 方法喚醒一個執行緒或者喚醒全部執行緒(單路通知);
- ReentrantLock提供了一種能夠中斷等待鎖的執行緒的機制,透過 lock.lockInterruptibly() 來實現這個機制。也就是說正在等待的執行緒可以選擇放棄等待,改為處理其他事情。而synchronized不具備這種特點。
- ReentrantLock: 通常提供更好的效能,特別是在高競爭環境下;
- 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哥!
如果您想與Build哥的關係更近一步,還可以關注“JavaBuild888”,在這裡除了看到《Java成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!