synchronized和ReentrantLock傻傻分不清楚

程序員小迷 2024-04-21 16:04:18

synchronized和ReentrantLock都是用于線程間同步的機制,都是可重入鎖(同一個線程可以多次獲取同一個鎖),它們的異同點如下:

一、應用場景

1.synchronized可應用于實例方法、靜態方法和代碼塊。

2.ReentrantLock 是 java.util.concurrent.locks 包下的一個具體類,實現了 Lock 接口。使用時需要顯式創建 ReentrantLock 對象並調用其方法。

二、鎖獲取與釋放機制

1.當進入或退出同步代碼時,synchronized自動獲取鎖釋放鎖,執行完畢或抛出異常時自動釋放鎖。

2.ReentrantLock需要手動調用lock()獲取鎖,調用unlock()釋放鎖。若沒有主動釋放鎖,可能導致死鎖。推薦使用 try-catch-finally 或 try-with-resources 結構來確保鎖的釋放。

三、嘗試非阻塞地獲取鎖:

1.synchronized 無法做到嘗試非阻塞地獲取鎖。

2.ReentrantLock 提供了tryLock()方法,該方法嘗試獲取鎖,如果成功則返回true,否則立即返回false,線程不會被阻塞。

四、鎖的公平性

1.synchronized是非公平的,即在鎖被釋放時,任何一個等待鎖的線程都有機會獲得鎖。

2.ReentrantLock默認情況下也是非公平的,但可以通過帶布爾值爲true的構造函數構造成公平鎖。當ReentrantLock被配置爲公平鎖時,則多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖。

五、條件等待與通知

1.synchronized 通過 Object 類的 wait(), notify(), notifyAll() 方法來實現線程間的條件等待與通知。這些方法必須在 synchronized 代碼塊或synchronized修飾的方法中調用,否則會抛出 IllegalMonitorStateException。

2.ReentrantLock 可以通過 newCondition() 方法創建多個條件變量 Condition 對象。線程可以調用 condition.await() 進行等待,其他線程調用 condition.signal() 或 condition.signalAll() 進行通知。這種方式支持更精細的線程間協作,可以避免"僞喚醒"問題,使得線程等待特定條件而不是僅僅等待鎖的釋放。

六、可中斷性

1.synchronized不支持正在等待鎖的線程被中斷。

2.ReentrantLock提供了lockInterruptibly()方法支持等待鎖的線程被中斷(若所在的線程被中斷,則會抛出異常並釋放當前獲得的鎖)。當持有鎖的線程長期不釋放鎖的時候,正在等待的線程可以選擇放棄等待,改爲處理其他事情。

七、可配置性

1.在synchronized中,鎖對象wait()、notify()或notifyAl()只能關聯一個隱含的條件Condition,若要和多于一個的條件Condition關聯不得不額外地添加一個鎖。不可設置超時時間。

2.一個ReentrantLock對象可以使用tryLock(long timeout, TimeUnit unit)設置超時時間(超時後線程不會一直阻塞,而是立即返回一個布爾值表示是否成功獲取鎖),可以使用getOwner()或isHeldByCurrentThread()判斷鎖是否被本線程或其他線程持有,可以使用getQueuedThreads()獲取等待此鎖的線程集合,可以使用getWaitingThreads(Condition condition)獲取等待在此鎖上的某個Condition上的線程集合 ,可以通過多次調用newCondition()同時綁定多個Condition對象實現線程等待/通知機制。

八、性能

在高並發場景下,ReentrantLock的性能可能優于synchronized,因爲它提供了更多的靈活性和控制選項。

九、鎖優化機制

1.synchronized 的實現涉及到鎖的升級,具體爲無鎖、偏向鎖、自旋鎖、重量級鎖。

2.ReentrantLock 的實現則是通過利用 CAS(Compare And Swap)自旋機制保證線程操作的原子性和 volatile 保證數據可見性以實現鎖的功能。

十、引申

1.synchronized 是JVM層面的同步,JVM會優化其性能,例如鎖消除、鎖粗化等。

2.ReentrantLock 是一個類,可以擴展,例如 ReentrantReadWriteLock 就是在 ReentrantLock 基礎上實現的讀寫鎖,提供了更複雜的讀寫權限控制。

3.synchronized在JVM中是采用 monitorenter 和 monitorexit 兩個指令來實現同步的,monitorenter 指令相當于加鎖,monitorexit 相當于釋放鎖。而 monitorenter 和 monitorexit 就是基于 Monitor 實現的。

4.ReentrantLock的常用的方法如下:

tryLock():嘗試獲取鎖

getHoldCount():查詢當前線程執行 lock() 方法的次數

getQueueLength():返回正在排隊等待獲取此鎖的線程數

isFair():該鎖是否爲公平鎖

hasQueuedThread(Thread thread):返回指定的線程是否在等待獲取此鎖

5.ReentrantLock中lock() 和 lockInterruptibly() 的區別:

獲取鎖的過程中如果所在的線程中斷,lock() 會忽略異常繼續等待獲取鎖,而 lockInterruptibly() 則會抛出 InterruptedException 異常。

6.synchronized實現鎖升級的過程:

在鎖對象的對象頭裏面有一個 ThreadID 字段,在第一次訪問的時候 ThreadID 爲空,然後JVM讓其持有偏向鎖,並將 ThreadID 設置爲調用鎖對象的線程 ID,再次進入的時候會先判斷 ThreadID 是否與其線程 ID 一致,如果一致則可以直接使用,如果不一致,則升級偏向鎖爲輕量級鎖,通過自旋來獲取鎖,不會阻塞,執行一定次數之後會升級爲重量級鎖(映射到操作系統提供的互斥鎖Mutex Lock上)。

7.可以通過設置JVM參數UseHeavyMonitors禁用偏向鎖和輕量級鎖,直接使用重量級鎖。

微風不燥,陽光正好,你就像風一樣經過這裏,願你停留的片刻溫暖舒心。我是程序員小迷(致力于C、C++、Java、Kotlin、Android、Shell、JavaScript、TypeScript、Python等編程技術的技巧經驗分享),若作品對您有幫助,請關注、分享、點贊、收藏、在看、喜歡,您的支持是我們爲您提供幫助的最大動力。

歡迎關注。助您在編程路上越走越好!

0 阅读:4

程序員小迷

簡介:致力于Android、C等編程技術的技巧經驗分享