## 22 到底哪把鎖更適合你?—synchronized與ReentrantLock對比
> 橫眉冷對千夫指,俯首甘為孺子牛。
> ——魯迅
前一節我們學習了ReentrantLock顯式鎖,在這之前我們還學習過 synchronized 內置鎖。那么這兩個鎖究竟有什么區別呢?synchronized是不是就足夠用了?什么時候使用ReentrantLock?本小節,我們就來一塊看看這兩種鎖,分析一下他們的不同之處,以及應用的最佳場景。
## 1、synchronized與ReentrantLock比較
從功能特性上來看,ReentrantLock其實具備synchronized所有特性,可以完全取代synchronized。不過ReentrantLock設計之初并不是為了替換掉synchronized,而是當synchronized不能滿足需求時,才考慮使用ReentrantLock。這是因為ReentrantLock使用起來需要更為小心,必須要顯式的釋放鎖。一旦忘記或者執行不到釋放鎖的代碼,那么其它線程無法獲取鎖,一直陷入等待之中。另外由于synchronized更被開發人員所熟知,并且編寫起來,代碼更為緊湊,非常的簡潔。只需要把同步代碼放入花括號中。執行完同步代碼塊中的代碼,鎖自動被釋放。因此,一般情況下,如果synchronized能夠滿足我們的需求,我們還是應該盡量使用synchronized。除非是需求確實需要顯式鎖Lock的相關特性,我們才會選擇使用顯式鎖。這就像我們挑選物品,當然功能多的最好,但你同時會面臨著高昂的價格、復雜的使用方法、更容易出現故障等問題。所以很簡單,我們應該按需選擇,如果不需要那么多功能,那么就選擇最簡單的易用的。

下面我們從幾個不同維度對synchronized和ReentrantLock做個對比。
## 2、性能對比
ReentrantLock 在 Java 5.0時被添加進來。那個時候,它有著比內置鎖更好的競爭性。競爭性是鎖的性能重點,有著好的競爭性,代表線程在鎖的競爭上消耗更低,整個并發程序的性能就會更好。不過在 Java 6 開始,內置鎖改進了算法,從而限制提高了內置鎖的性能。
5.0 時,隨著線程的增加,內置鎖的性能急劇下降,而 ReentrantLock 的下降并不明顯。線程增加到一定數量后,ReentrantLock 性能會達到內置鎖的 4-5 倍。而在 6.0 中,兩者差距并不明顯,ReentrantLock 略占一點點優勢。
所以結論是我們并不需要過多考慮性能因素,而采用 ReentrantLock。
## 3、特性對比
可以說 ReentrantLock 在特性上完勝內置鎖。ReentrantLock 提供了公平和非公平鎖、可定時、可輪詢和可中斷的鎖獲取方式、非塊狀鎖結構。如果我們真的需要使用這些特性,那么不要猶豫,去使用 ReentrantLock 就好,因為 synchronized 根本就不支持。
## 4、公平性的選擇
這個比較簡單直接,ReentrantLock 支持公平鎖,而內置鎖不能支持公平鎖。ReentrantLock 內部有一個線程排隊的隊列,如果 ReentrantLock 選擇了公平的方式,那么隊列中的線程會按照順序去 tryLock。非公平的方式,在鎖釋放后,如果有新的線程來競爭鎖,那么就可能插隊,在等待隊列中的線程被恢復并獲取鎖之前,新的線程獲取了鎖。
公平性的選擇,意味著需要放棄一部分性能。大多數情況下,公平鎖的性能都要低于非公平鎖。這是因為掛起和恢復線程都有很大開銷。選擇公平鎖時,從釋放鎖到等待隊列中最前面線程被喚醒能夠去 tryLock,中間有很大的時間延遲,那么這就造成了公平鎖的性能會更差。
如果線程獲取鎖到釋放鎖之間的程序執行時間較長,那么公平鎖的性能不會那么差。因為不會有很多的線程喚醒操作,也就是說不會有過多的時間間隙被浪費點。那么公平鎖有能帶來更好的公平性,所以此時我們優先選擇公平鎖。
如果線程持有鎖執行邏輯的時間很短,而多線程并發量又很大。這造成了獲取和釋放鎖頻繁發生,從而大量時間浪費在從鎖被釋放到排隊線程被喚醒工作的過程上。因此,此時我們更好的選擇是非公平鎖。
## 5、使用上的差異
兩者在使用上的差異前文已經有所對比。synchronized 使用簡單,只需要把同步代碼放入 synchronized 代碼塊中即可,程序執行完同步代碼塊自動解鎖。而 Lock 需要顯式獲取鎖,然后需要配合 try 和 finally 來使用。尤其注意一定要在 finally 中釋放鎖。從使用角度看,我們應該優先使用 synchronized,因為更為方便,也不容易出錯。
## 6、總結
本節我們從幾個不同緯度對比了 synchronized 和 lock。首先在性能上,Java 6 以后兩者并沒有顯著的區別。在功能上,Lock 顯然更為豐富,適合更多的場景。不過在絕大多數場景中,synchronized 提供的功能完全能夠滿足。此外,synchronized的 使用更為簡單和安全。因此,如果我們并不需要lock提供的額外功能,那么請優先使用synchronized 方法。只有在真的需要 lock 所提供的特性時,才應選擇 lock。下一節我們來看看 Lock 家族的另外一員大將,更為靈活的讀寫鎖——ReadWriteLock。
- 前言
- 第1章 Java并發簡介
- 01 開篇詞:多線程為什么是你必需要掌握的知識
- 02 絕對不僅僅是為了面試—我們為什么需要學習多線程
- 03 多線程開發如此簡單—Java中如何編寫多線程程序
- 04 人多力量未必大—并發可能會遇到的問題
- 第2章 Java中如何編寫多線程
- 05 看若兄弟,實如父子—Thread和Runnable詳解
- 06 線程什么時候開始真正執行?—線程的狀態詳解
- 07 深入Thread類—線程API精講
- 08 集體協作,什么最重要?溝通!—線程的等待和通知
- 09 使用多線程實現分工、解耦、緩沖—生產者、消費者實戰
- 第3章 并發的問題和原因詳解
- 10 有福同享,有難同當—原子性
- 11 眼見不實—可見性
- 12 什么?還有這種操作!—有序性
- 13 問題的根源—Java內存模型簡介
- 14 僵持不下—死鎖詳解
- 第4章 如何解決并發問題
- 15 原子性輕量級實現—深入理解Atomic與CAS
- 16 讓你眼見為實—volatile詳解
- 17 資源有限,請排隊等候—Synchronized使用、原理及缺陷
- 18 線程作用域內共享變量—深入解析ThreadLocal
- 第5章 線程池
- 19 自己動手豐衣足食—簡單線程池實現
- 20 其實不用造輪子—Executor框架詳解
- 第6章 主要并發工具類
- 21 更高級的鎖—深入解析Lock
- 22 到底哪把鎖更適合你?—synchronized與ReentrantLock對比
- 23 按需上鎖—ReadWriteLock詳解
- 24 經典并發容器,多線程面試必備—深入解析ConcurrentHashMap上
- 25 經典并發容器,多線程面試必備—深入解析ConcurrentHashMap下
- 26不讓我進門,我就在門口一直等!—BlockingQueue和ArrayBlockingQueue
- 27 倒數計時開始,三、二、一—CountDownLatch詳解
- 28 人齊了,一起行動—CyclicBarrier詳解
- 29 一手交錢,一手交貨—Exchanger詳解
- 30 限量供應,不好意思您來晚了—Semaphore詳解
- 第7章 高級并發工具類及并發設計模式
- 31 憑票取餐—Future模式詳解
- 32 請按到場順序發言—Completion Service詳解
- 33 分階段執行你的任務-學習使用Phaser運行多階段任務
- 34 誰都不能偷懶-通過 CompletableFuture 組裝你的異步計算單元
- 35 拆分你的任務—學習使用Fork/Join框架
- 36 為多線程們安排一位經理—Master/Slave模式詳解
- 第8章 總結
- 37 結束語