淺談--Lock鎖 與 Condition
一 Lock鎖簡介
1.用處:
Lock鎖與synchronized一樣,都是可以用來控制同步訪問的。
2.有了synchronized,為什麼還要Lock鎖呢?
那就要談到synchronized的缺點,主要是三個方面。
A 有時候用synchronized修飾的程式碼,訪問它需要很長時間,下一個要訪問同一程式碼塊的執行緒就要等待阻塞很長的時間。如果我想要下一個執行緒在等待一段時間後,如果還沒有得到鎖的話,就放棄等待,這就可以使用Lock鎖,來設定等待時間。
B synchronized 是互斥鎖,同一時間只能有一個執行緒可以訪問被它修飾的程式碼塊。而Lock鎖可以實現互斥鎖,也可以實現共享鎖(同一時間支援多條執行緒訪問)。
C 有些情況下,獲取與釋放鎖的情況比較複雜。比如:用於遍歷併發訪問的資料結構的一些演算法需要使用“手動”或“鏈鎖定”:您獲取節點A的鎖定,然後獲取節點B,然後釋放A並獲取C,然後釋放B並獲得D等。在這種場景中synchronized關鍵字就不那麼容易實現了,使用Lock介面容易很多
3.Lock鎖的缺點:
相比於synchronized,Lock需要手動的獲取鎖與釋放鎖。
4.Lock鎖的高階特性:
A 嘗試非阻塞地獲取鎖 當前執行緒嘗試獲取鎖,如果這一時刻鎖沒有被其他執行緒獲取到,則成功獲取並持有鎖,如果當前時刻,鎖被其它執行緒佔有,則直接返回fasle;
B 能被中斷地獲取鎖 在鎖的獲取過程中可以響應中斷。呼叫 lockInterruptibly() 方法,當通過這個方法去獲取鎖時,如果執行緒正在等待獲取鎖,則這個執行緒能夠響應中斷,即中斷執行緒的等待狀態。也就使說,當兩個執行緒同時通過 lock.lockInterruptibly()想獲取某個鎖時,假若此時執行緒A獲取到了鎖,而執行緒B只有在等待,那麼對執行緒B呼叫 threadB.interrupt()方法能夠中斷執行緒B的等待過程。
注意,當一個執行緒獲取了鎖之後,是不會被interrupt()方法中斷的。因為本身在前面的文章中講過單獨呼叫interrupt()方法不能中斷正在執行過程中的執行緒,只能中斷阻塞過程中的執行緒。
因此當通過lockInterruptibly()方法獲取某個鎖時,如果不能獲取到,只有進行等待的情況下,是可以響應中斷的。
而用synchronized修飾的話,當一個執行緒處於等待某個鎖的狀態,是無法被中斷的,只有一直等待下去。
C 超時獲取鎖 在指定的截止時間之前獲取鎖, 超過截止時間後仍舊無法獲取則返回
二 ReentrantLock
ReentrantLock作為Lock介面的子類,可以實現Lock介面的所有特性。內部有兩個同步器實現類,一個為公平鎖,另一個為非公平鎖。
1.特性:
ReentrantLock的特性就是支援重進入,即任何執行緒在獲取到鎖之後能夠再次獲取該鎖而不會發生堵塞,注意是兩次獲取的是同一個鎖。
ReentrantLock還支援定義公平鎖與非公平鎖,關於同步器與公平鎖,可以看一下 https://mp.csdn.net/postedit/85238441
2.使用API
ReentrantLock lock = new ReentrantLock(true) 定義是否為公平鎖。
lock.tryLock(); 非阻塞 的獲取鎖。
lock.lockInterruptibly();可中斷的獲取鎖
tryLock(long timeout, TimeUnit unit); 設定時間的獲取鎖。
lock。unlock(); 釋放鎖。
三
ReentrantReadWriteLock
ReentrantReadWriteLock是讀寫鎖,把對一個資源的鎖分為讀鎖與寫鎖。
1.特性
ReentrantReadWriteLock內部維護了一對鎖。一個是讀鎖,是共享鎖,同一時刻可以允許多個執行緒同時進行訪問,
還有一個是寫鎖,是排它鎖,同一時刻只允許一個執行緒訪問。
當寫鎖被獲取到時,後續的讀寫操作都會被阻塞,當寫鎖釋放後,所有的操作繼續執行。
ReentrantReadWriteLock也支援重進入與公平性選擇。
2.API
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(false); 建立公平鎖與非公平鎖。
ReentrantReadWriteLock.ReadLock readLock = lock.readLock(); 獲取讀鎖。
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); 獲取寫鎖。
其他的API實現Lock 的高階特性,如非阻塞的獲取鎖,可中斷的獲取鎖。
3.鎖降級
當前執行緒遵循先獲取寫鎖、獲取讀鎖、釋放寫鎖 、釋放讀鎖的過程,稱為鎖降級
目的:為了保證資料的可見性。如果當前執行緒不獲取讀鎖,而是直接釋放寫鎖。假設此時有另一執行緒T獲取寫鎖,並修改了資料。則當前執行緒無法感知到執行緒T的資料更新。如果遵循鎖降級的步驟,執行緒T將會被阻塞,直到當前執行緒使用資料並釋放讀鎖之後,執行緒T才能獲取寫鎖進行資料更新。
四 Condition
Condition作為介面使用的是ConditionObject物件,ConditionObject是AQS的內部類,每個Condition物件都有一個FIFO等待佇列,用於儲存處於等待狀態的執行緒。
1.作用:
可以實現比 wait()和notify/notifyAll()方法更高階的 等待/通知機制
2 wait()和notify/notifyAll()的介紹
在使用notify/notifyAll()方法進行通知時,被通知的執行緒是有JVM選擇的,
執行notifyAll()方法的話就會通知所有處於等待狀態的執行緒
3.Condition
一個Lock物件中可以建立多個Condition例項,一個Condition可以註冊多個執行緒,從而可以有選擇性的進行執行緒通知
而synchronized關鍵字就相當於整個Lock物件中只有一個Condition例項,所有的執行緒都註冊在它一個身上。
Condition例項的signalAll()方法 只會喚醒註冊在該Condition例項中的所有等待執行緒
4.用法
Condition condition1 = lock.newCondition(); 建立Condition
condition1.await(); 使當前執行緒釋放鎖,構造成節點,加入等待佇列尾部,進入等待狀態。
condition1.signal(); 喚醒註冊在此Condition上等待時間最長的執行緒。即喚醒在等待佇列首節點的執行緒
condition1.signalAll(); 喚醒註冊在此Condition上所有等待的執行緒。
5.注意
A 必須在condition.await()方法呼叫之前呼叫lock.lock()程式碼獲得鎖,不然會報錯。
B 在使用wait/notify實現等待通知機制的時候我們知道必須執行完notify()方法所在的synchronized程式碼塊後才釋放鎖。在這裡也差不多,必須執行完signal所在的try語句塊之後才釋放鎖,condition.await()後的語句才能被執行。
6.關於同步佇列與等待佇列的問題
Object模型只有一條同步佇列與一條等待佇列,所有處於等待的執行緒都位於等待佇列中。呼叫await()方法時,由於此時已經在同步佇列中,並獲取了鎖,使當前執行緒釋放鎖,構造成節點,加入等待佇列尾部,進入等待狀態。
當從等待佇列中將執行緒喚醒時,是隨機喚醒的(synchronized 與 notify組合),使等待佇列中被喚醒的執行緒,加入到同步佇列中,進入鎖的獲取競爭中。成功獲取了鎖的執行緒,才從await()方法後開始執行。
Lock模型有一條同步佇列與多條等待佇列,即上文關於Condition與synchronized的分析。加入等待佇列與喚醒執行緒與Object模型相同,只是當喚醒執行緒時,不是隨機通知的,而是喚醒在等待佇列中等待時間最長的執行緒,即等待佇列中的首節點。
歡迎點贊與關注小編,小編水平有限,錯誤的地方請指出來。
歡迎評論與討論,小編會盡力解答。