sychronized (monitor監視器) -- 自旋獲取鎖形式
把代碼塊聲明為 synchronized,有兩個重要后果,通常是指該代碼具有 原子性(atomicity)和 可見性(visibility)。原子性意味着一個線程一次只能執行由一個指定監控對象(lock)保護的代碼,從而防止多個線程在更新共享狀態時相互沖突。可見性則更為微妙;它要對付內存緩存和編譯器優化的各種反常行為。
monitorenter & monitorexit
ReentranLock -- 阻塞獲取鎖形式
Lock
框架是鎖定的一個抽象,它允許把鎖定的實現作為 Java 類,而不是作為語言的特性來實現。這就為 Lock
的多種實現留下了空間,各種實現可能有不同的調度算法、性能特性或者鎖定語義。 ReentrantLock
類實現了 Lock
,它擁有與 synchronized
相同的並發性和內存語義,但是添加了類似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用情況下更佳的性能。(換句話說,當許多線程都想訪問共享資源時,JVM 可以花更少的時候來調度線程,把更多時間用在執行線程上。)
reentrant 鎖意味着什么呢?簡單來說,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那么獲取計數器就加1,然后鎖需要被釋放兩次才能獲得真正釋放。這模仿了 synchronized
的語義;如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者后續) synchronized
塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個 synchronized
塊時,才釋放鎖。
ReentrantLock
構造器的一個參數是 boolean 值,它允許您選擇想要一個 公平(fair)鎖,還是一個 不公平(unfair)鎖。公平鎖使線程按照請求鎖的順序依次獲得鎖;而不公平鎖則允許討價還價,在這種情況下,線程有時可以比先請求鎖的其他線程先得到鎖。在現實中,公平保證了鎖是非常健壯的鎖,有很大的性能成本。要確保公平所需要的記帳(bookkeeping)和同步,就意味着被爭奪的公平鎖要比不公平鎖的吞吐率更低。作為默認設置,應當把公平設置為 false
,除非公平對您的算法至關重要,需要嚴格按照線程排隊的順序對其進行服務。
在確實需要一些 synchronized 所沒有的特性的時候,比如時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變量或者鎖投票。 ReentrantLock
還具有可伸縮性的好處,應當在高度爭用的情況下使用它,但是請記住,大多數 synchronized 塊幾乎從來沒有出現過爭用,所以可以把高度爭用放在一邊。我建議用 synchronized 開發,直到確實證明 synchronized 不合適,而不要僅僅是假設如果使用 ReentrantLock
“性能會更好”。請記住,這些是供高級用戶使用的高級工具。(而且,真正的高級用戶喜歡選擇能夠找到的最簡單工具,直到他們認為簡單的工具不適用為止。)。一如既往,首先要把事情做好,然后再考慮是不是有必要做得更快。
ReentrantLock之lock形式 -- for (;;){} 循環獲取鎖 -- 自旋
1、根據構造器創建lock,設定為公平鎖or不公平鎖
2、lock,獲取鎖
添加至Node隊列,包含上Node地址,下Node地址,以及線程信息
獲取鎖隊列,從head位開始,嘗試去獲取鎖
獲取方式:獲取當前線程信息,得到鎖當前狀態,對處於state為0的,通過CAS操作,成功后將當前線程設置至可執行線程ExclusiveOwnerThread 。
如果失敗,調用Thread的interrupted(),中斷線程
3、unlock
更改狀態,將ExclusiveOwnerThread 置為空
ReentrantLock擴展的功能
在內部鎖中,死鎖是致命的——唯一的恢復方法是重新啟動程序,唯一的預防方法是在構建程序時不要出錯。而可輪詢的鎖獲取模式具有更完善的錯誤恢復機制,可以規避死鎖的發生。
如果你不能獲得所有需要的鎖,那么使用可輪詢的獲取方式使你能夠重新拿到控制權,它會釋放你已經獲得的這些鎖,然后再重新嘗試。可輪詢的鎖獲取模式,由tryLock()方法實現。此方法僅在調用時鎖為空閑狀態才獲取該鎖。如果鎖可用,則獲取鎖,並立即返回值true。如果鎖不可用,則此方法將立即返回值false。此方法的典型使用語句如下:
Lock lock = ...; if (lock.tryLock()) { try { // manipulate protected state } finally { lock.unlock(); } } else { // perform alternative actions }
實現可定時的鎖請求
動調用了阻塞方法,定時鎖能夠在時間預算內設定相應的超時。如果活動在期待的時間內沒能獲得結果,定時鎖能使程序提前返回。可定時的鎖獲取模式,由tryLock(long, TimeUnit)方法實現。
只在時間范圍內去獲取鎖,超出時間則認為無法獲取鎖。
實現可中斷的鎖獲取請求
可中斷的鎖獲取操作允許在可取消的活動中使用。lockInterruptibly()方法能夠使你獲得鎖的時候響應中斷。
ReentrantLock不好與需要注意的地方
Object.wait
做的那樣。
完整的await()操作是安裝如下步驟進行的:
- 將當前線程加入Condition鎖隊列。特別說明的是,這里不同於AQS的隊列,這里進入的是Condition的FIFO隊列。進行2。
- 釋放鎖。這里可以看到將鎖釋放了,否則別的線程就無法拿到鎖而發生死鎖。進行3。
- 自旋(while)掛起,直到被喚醒或者超時或者CACELLED等。進行4。
- 獲取鎖(acquireQueued)。並將自己從Condition的FIFO隊列中釋放,表明自己不再需要鎖(我已經拿到鎖了)
FIFO(固定長度隊列)
AQS的全稱為(AbstractQueuedSynchronizer)
AQS是鏈表,有一個head引用來指向鏈表的頭節點,AQS在初始化的時候head、tail都是null,在運行時來回移動。此時,我們最少至少知道AQS是一個基於狀態(state)的鏈表管理方式。
列的結構就變成了以下這種情況了,通過這樣的方式,就可以讓執行完的節點釋放掉內存區域,而不是無限制增長隊列,也就真正形成FIFO了。
AQS獲取鎖是通過tryAcquire去獲取的,本身並沒有獲取方式,釋放鎖是release,通過tryRelease。
原理:
ReentraneLock 是Lock的一種實現,也是java層次鎖的體現,而非synchronized語言特性的形式。ReentraneLock最基本的核心在於AQS,AbstractQuenedSynchronizer提供了一系列的模板方法,
通過將lock和unlock操作分別交給子類去實現,AQS提供了公平鎖和非公平鎖2種模式。而ReentraneLock是使用的Sync,是AQS的一個子類,使用的是非公平鎖。
AQS會把請求線程放入一個CLH隊列中,由線程去嘗試獲取鎖,如果成功,則將當前運行線程設置為該線程,並將state狀態從0設置為1,再重入則會繼續加1。如果線程嘗試去獲取鎖的時候,發現已經有
線程將鎖獨占,這時AQS會將線程包裝為一個Node放入CLH隊列的tail處,然后對線程進行阻塞。在阻塞前會再次的請求獲取鎖,如果還是沒有獲取到鎖,則將線程阻塞。
CLH隊列的含義:CLH為隊列鎖,隊列中的Node對標注為是否需要獲取鎖。