可重入鎖允許一個線程多次獲取同一把鎖,避免線程自身被鎖死。1.reentrantlock基于aqs實現,通過cas操作和fifo隊列管理鎖的獲取與釋放;2.可重入性通過判斷當前線程是否為鎖持有者實現,state值記錄重入次數;3.釋放鎖時減1,state為0才喚醒等待線程;4.可重入性在一定程度上避免死鎖,但無法完全解決;5.公平鎖按請求順序分配,非公平鎖允許插隊,性能更高但可能導致饑餓;6.選擇reentrantlock可獲得更靈活控制、公平鎖、中斷響應等功能,而synchronized則適合簡單場景、自動釋放鎖且可能優化性能。
可重入鎖,簡單來說,就是允許一個線程多次獲取同一把鎖。這在Java中非常重要,尤其是在復雜的并發場景下,它避免了自己把自己鎖死的情況,讓代碼更靈活。
ReentrantLock的實現原理
ReentrantLock的核心是AQS(AbstractQueuedSynchronizer)。AQS維護了一個同步狀態(state)和一個FIFO的等待隊列。
立即學習“Java免費學習筆記(深入)”;
-
獲取鎖: 當一個線程嘗試獲取鎖時,首先會嘗試使用CAS(Compare and Swap)原子操作將state從0變為1。如果成功,則該線程獲取鎖,并將鎖的持有者設置為當前線程。如果state不為0,則檢查當前線程是否為鎖的持有者。如果是,則將state加1,表示重入次數加1。如果CAS失敗,并且當前線程不是鎖的持有者,則該線程會被放入AQS的等待隊列中,等待被喚醒。
-
釋放鎖: 當線程釋放鎖時,首先檢查當前線程是否為鎖的持有者。如果不是,則拋出異常。如果是,則將state減1。如果state變為0,則表示鎖完全釋放,會喚醒等待隊列中的一個線程,讓其嘗試獲取鎖。
-
可重入性: ReentrantLock之所以可重入,是因為它在獲取鎖的時候,會檢查當前線程是否為鎖的持有者。如果是,則允許重入,并將state加1。這樣,同一個線程就可以多次獲取同一個鎖,而不會被阻塞。
ReentrantLock如何避免死鎖?
死鎖往往是因為多個線程互相持有對方需要的資源,導致大家都無法繼續執行。ReentrantLock的可重入性,在一定程度上避免了死鎖的發生。例如,一個線程在已經持有鎖的情況下,如果需要再次獲取該鎖,由于ReentrantLock的可重入性,它可以直接獲取,而不會被阻塞。
然而,僅僅依靠ReentrantLock的可重入性并不能完全避免死鎖。仍然需要注意鎖的獲取順序,以及避免循環依賴等問題。
公平鎖與非公平鎖的區別?
ReentrantLock可以配置為公平鎖或非公平鎖。
-
公平鎖: 按照線程請求鎖的順序來分配鎖。也就是說,等待隊列中最先進入的線程會優先獲取鎖。公平鎖保證了所有線程都有機會獲取鎖,避免了某些線程長時間饑餓的情況。但公平鎖的性能相對較低,因為需要維護等待隊列的順序。
-
非公平鎖: 允許線程“插隊”。也就是說,當鎖被釋放時,如果有線程正在嘗試獲取鎖,它可以直接獲取鎖,而不需要等待進入等待隊列。非公平鎖的性能相對較高,因為減少了線程切換的開銷。但非公平鎖可能導致某些線程長時間饑餓。
選擇公平鎖還是非公平鎖,取決于具體的應用場景。如果對公平性要求較高,可以選擇公平鎖。如果對性能要求較高,可以選擇非公平鎖。大多數情況下,非公平鎖是更好的選擇。
如何選擇ReentrantLock而不是synchronized?
synchronized是Java內置的同步機制,而ReentrantLock是一個類,提供了更靈活的鎖控制。
選擇ReentrantLock的情況:
- 需要更細粒度的控制: ReentrantLock提供了諸如tryLock()(嘗試獲取鎖)、lockInterruptibly()(可中斷的獲取鎖)等方法,可以更靈活地控制鎖的獲取和釋放。
- 需要公平鎖: synchronized是非公平鎖,而ReentrantLock可以配置為公平鎖。
- 需要中斷鎖的等待: 使用lockInterruptibly()可以在等待鎖的過程中響應中斷。
- 需要知道是否獲取到了鎖: 使用tryLock()可以嘗試獲取鎖,并立即返回是否獲取成功,而不會一直阻塞。
選擇synchronized的情況:
- 代碼更簡潔: synchronized使用起來更簡單,只需要使用synchronized關鍵字即可。
- 性能優化: 在某些情況下,jvm會對synchronized進行優化,使其性能與ReentrantLock相差無幾,甚至更好。
- 避免忘記釋放鎖: synchronized會自動釋放鎖,而ReentrantLock需要手動釋放鎖,容易忘記。
總的來說,synchronized更適合簡單的同步場景,而ReentrantLock更適合復雜的同步場景。需要根據具體情況進行選擇。