死磕 java同步系列之ReentrantLock VS 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也拿來一起對比了)
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的效能提升。
推薦閱讀
歡迎關注我的公眾號“彤哥讀原始碼”,檢視更多原始碼系列文章, 與彤哥一起暢遊原始碼的海洋。
相關推薦
死磕 java同步系列之ReentrantLock VS synchronized——結果可能跟你想的不一樣
問題 (1)ReentrantLock有哪些優點? (2)ReentrantLock有哪些缺點? (3)ReentrantLock
死磕 java同步系列之ReentrantLock原始碼解析(一)——公平鎖、非公平鎖
問題 (1)重入鎖是什麼? (2)ReentrantLock如何實現重入鎖? (3)ReentrantLock為什麼預設是非公平模式? (4)ReentrantLock除了可重入還有哪些特性? 簡介 Reentrant = Re + entrant,Re是重複、又、再的意思,entrant是enter的名詞或
死磕 java同步系列之ReentrantLock原始碼解析(二)——條件鎖
問題 (1)條件鎖是什麼? (2)條件鎖適用於什麼場景? (3)條件鎖的await()是在其它執行緒signal()的時候喚醒的嗎? 簡介 條件鎖,是指在獲取鎖之後發現當前業務場景自己無法處理,而需要等待某個條件的出現才可以繼續處理時使用的一種鎖。 比如,在阻塞佇列中,當佇列中沒有元素的時候是無法彈出一個元素
死磕 java同步系列之開篇
討論 關註 使用 避免死鎖 更新數據 讀寫 上下文切換 monit 缺點 簡介 同步系列,這是彤哥想了好久的名字,本來是準備寫鎖相關的內容,但是java中的CountDownLatch、Semaphore、CyclicBarrier這些類又不屬於鎖,它們和鎖又有很多共同點,
死磕 java同步系列之JMM(Java Memory Model)
簡介 Java記憶體模型是在硬體記憶體模型上的更高層的抽象,它遮蔽了各種硬體和作業系統訪問的差異性,保證了Java程式在各種平臺下對記憶體的訪問都能達到一致的效果。 硬體記憶體模型 在正式講解Java的記憶體模型之前,我們有必要先了解一下硬體層面的一些東西。 在現代計算機的硬體體系中,CPU的運算速度是非常快
死磕 java同步系列之volatile解析
問題 (1)volatile是如何保證可見性的? (2)volatile是如何禁止重排序的? (3)volatile的實現原理? (4)volatile的缺陷? 簡介 volatile可以說是Java虛擬機器提供的最輕量級的同步機制了,但是它並不容易被正確地理解,以至於很多人不習慣使用它,遇到多執行緒問題一律
死磕 java同步系列之synchronized解析
問題 (1)synchronized的特性? (2)synchronized的實現原理? (3)synchronized是否可重入? (4)synchronized是否是公平鎖? (5)synchronized的優化? (6)synchronized的五種使用方式? 簡介 synchronized關鍵字是Ja
死磕 java同步系列之自己動手寫一個鎖Lock
問題 (1)自己動手寫一個鎖需要哪些知識? (2)自己動手寫一個鎖到底有多簡單? (3)自己能不能寫出來一個完美的鎖? 簡介 本篇文章的目標一是自己動手寫一個鎖,這個鎖的功能很簡單,能進行正常的加鎖、解鎖操作。 本篇文章的目標二是通過自己動手寫一個鎖,能更好地理解後面章節將要學習的AQS及各種同步器實現的原理
死磕 java同步系列之AQS起篇
問題 (1)AQS是什麼? (2)AQS的定位? (3)AQS的實現原理? (4)基於AQS實現自己的鎖? 簡介 AQS的全稱是AbstractQueuedSynchronizer,它的定位是為Java中幾乎所有的鎖和同步器提供一個基礎框架。 AQS是基於FIFO的佇列實現的,並且內部維護了一個狀態變數sta
死磕 java同步系列之ReentrantReadWriteLock原始碼解析
問題 (1)讀寫鎖是什麼? (2)讀寫鎖具有哪些特性? (3)ReentrantReadWriteLock是怎麼實現讀寫鎖的? (4)如何使用ReentrantReadWriteLock實現高效安全的TreeMap? 簡介 讀寫鎖是一種特殊的鎖,它把對共享資源的訪問分為讀訪問和寫訪問,多個執行緒可以同時對共享
死磕 java同步系列之Semaphore原始碼解析
問題 (1)Semaphore是什麼? (2)Semaphore具有哪些特性? (3)Semaphore通常使用在什麼場景中? (
死磕 java同步系列之AQS終篇(面試)
問題 (1)AQS的定位? (2)AQS的重要組成部分? (3)AQS運用的設計模式? (4)AQS的總體流程? 簡介 AQS的全稱是AbstractQueuedSynchronizer,它的定位是為Java中幾乎所有的鎖和同步器提供一個基礎框架。 在之前的章節中,我們一起學習了ReentrantLock、R
死磕 java同步系列之StampedLock原始碼解析
問題 (1)StampedLock是什麼? (2)StampedLock具有什麼特性? (3)StampedLock是否支援可重入
死磕 java同步系列之CyclicBarrier原始碼解析——有圖有真相
問題 (1)CyclicBarrier是什麼? (2)CyclicBarrier具有什麼特性? (3)CyclicBarrier與
死磕 java同步系列之Phaser原始碼解析
問題 (1)Phaser是什麼? (2)Phaser具有哪些特性? (3)Phaser相對於CyclicBarrier和Count
死磕 java同步系列之mysql分散式鎖
問題 (1)什麼是分散式鎖? (2)為什麼需要分散式鎖? (3)mysql如何實現分散式鎖? (4)mysql分散式鎖的優點和缺點? 簡介 隨著併發量的不斷增加,單機的服務遲早要向多節點或者微服務進化,這時候原來單機模式下使用的synchronized或者ReentrantLock將不再適用,我們迫切地需要一
死磕 java同步系列之zookeeper分散式鎖
(2)zookeeper分散式鎖有哪些優點? (3)zookeeper分散式鎖有哪些缺點? 簡介 zooKeeper是一個分散式的,開放原始碼的分散式應用程式協調服務,它可以為分散式應用提供一致性服務,它是Hadoop和Hbase的重要元件,同時也可以作為配置中心、註冊中心運用在微服務體系中。 本章我們將介
死磕 java同步系列之redis分散式鎖進化史
(2)redis分散式鎖有哪些優點? (3)redis分散式鎖有哪些缺點? (4)redis實現分散式鎖有沒有現成的輪子可以使用? 簡介 Redis(全稱:Remote Dictionary Server 遠端字典服務)是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-
死磕 java同步系列之終結篇
腦圖 下面是關於同步系列的一份腦圖,列舉了主要的知識點和問題點,看過本系列文章的同學可以根據腦圖自行回顧所學的內容,也可以作為面試前的準備。 如果有需要高清無碼原圖的同學,可以關注公眾號“彤哥讀原始碼”,回覆“sync”領取。 總結 所謂同步,就是保證多執行緒(包括多程序)對共享資源的讀寫能夠安全有效的執