java.util.concurrent.Locks使用指南
1.概述
簡而言之,鎖是一種比標準同步塊更靈活,更復雜的線程同步機制。
自Java 1.5以來,Lock接口一直存在。它在java.util.concurrent.lock包中定義,它提供了大量的鎖定操作。
在本文中,我們將探討Lock接口及其應用程序的不同實現。
2. 鎖定(Lock)和同步塊(Synchronized )之間的差異
- 同步塊被完全包含在方法中(創建和釋放鎖在同一個方法內); Lock可以將lock() 和 unlock()放在不同的方法中
- synchronized塊不支持公平性,一旦鎖釋放,任何線程可能獲得鎖; 通過指定公平屬性,我們可以在
- 如果線程無法訪問同步塊,則會阻塞該線程; Lock API提供的tryLock()方法,只有當鎖可用且沒有任何其他線程持有時,線程才會獲取鎖定。這減少了線程等待鎖定的阻塞時間(可以設置嘗試獲取鎖的時間)
- 處於“等待”狀態以獲取對同步塊的訪問的線程不能被中斷; Lock API提供了一種方法lockInterruptibly() ,其可以用來中斷等待鎖線程。
3. Lock API
我們來看看Lock接口中的方法:
- void lock() -獲取鎖(如果可用);
- void lockInterruptibly() - 這類似於 lock(),但它允許被阻塞的線程被中斷並通過拋出的java.lang.InterruptedException恢復執行
- boolean tryLock() - 這是 lock()方法的非阻塞版本; 它會立即嘗試獲取鎖定,如果鎖定成功則返回true
- boolean tryLock(long timeout,TimeUnit timeUnit) -這類似於 tryLock(),除了它在放棄嘗試獲取 Lock之前等待給定的超時
- void unlock() - 解鎖Lock實例
執行完畢應始終釋放鎖以避免死鎖情況。
Lock lock = ...; lock.lock(); try { // access to the shared resource } finally { lock.unlock(); }
除了 Lock接口之外,我們還有一個ReadWriteLock接口,它維護一對鎖,一個用於只讀操作,另一個用於寫操作。只要沒有寫入,讀鎖可以由多個線程同時保持。
ReadWriteLock聲明獲取讀或寫鎖的方法:
- Lock readLock() -返回用於讀取的鎖
- Lock writeLock() - 返回用於寫入的鎖
4. 鎖的實現
4.1.ReentrantLock(重入鎖)
ReentrantLock類實現Lock接口。它提供了相同的並發和內存語義.
public class SharedObject { //... ReentrantLock lock = new ReentrantLock(); int counter = 0; public void perform() { lock.lock(); try { // Critical section here count++; } finally { lock.unlock(); } } //... }
我們需要確保在try-finally塊中包裝lock()和unlock()調用以避免死鎖情況。
讓我們看看tryLock()如何使用:
public void performTryLock(){ //... boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS); if(isLockAcquired) { try { //Critical section here } finally { lock.unlock(); } } //... }
在這種情況下,調用tryLock()的線程將最多等待一秒鐘(如果直接拿到鎖則不用等待),並且如果鎖不可用則放棄等待。
4.2.ReentrantReadWriteLock
ReentrantReadWriteLock類實現ReadWriteLock接口。
讓我們看一下線程獲取ReadLock或WriteLock的規則:
- 讀鎖 - 如果沒有線程獲得寫鎖或請求它,則多個線程可以獲取讀鎖
- 寫鎖- 如果沒有線程正在讀取或寫入,則只有一個線程可以獲取寫入鎖
讓我們看看如何使用ReadWriteLock:
public class SynchronizedHashMapWithReadWriteLock { Map<String,String> syncHashMap = new HashMap<>(); ReadWriteLock lock = new ReentrantReadWriteLock(); // ... Lock writeLock = lock.writeLock(); public void put(String key, String value) { try { writeLock.lock(); syncHashMap.put(key, value); } finally { writeLock.unlock(); } } ... public String remove(String key){ try { writeLock.lock(); return syncHashMap.remove(key); } finally { writeLock.unlock(); } } //... }
Lock readLock = lock.readLock(); //... public String get(String key){ try { readLock.lock(); return syncHashMap.get(key); } finally { readLock.unlock(); } } public boolean containsKey(String key) { try { readLock.lock(); return syncHashMap.containsKey(key); } finally { readLock.unlock(); } }
4.3.StampedLock
StampedLock在Java 8中引入。它還支持讀寫鎖定。但是,鎖獲取方法會返回一個用於釋放鎖或檢查鎖是否仍然有效的戳記:
public class StampedLockDemo { Map<String,String> map = new HashMap<>(); private StampedLock lock = new StampedLock(); public void put(String key, String value){ long stamp = lock.writeLock(); try { map.put(key, value); } finally { lock.unlockWrite(stamp); } } public String get(String key) throws InterruptedException { long stamp = lock.readLock(); try { return map.get(key); } finally { lock.unlockRead(stamp); } } }
StampedLock提供的另一個功能是樂觀鎖定。大多數時候,讀操作不需要等待寫操作完成,因此不需要真正的讀鎖。
如果出現並發,我們可以升級到讀鎖:
public String readWithOptimisticLock(String key) { long stamp = lock.tryOptimisticRead(); String value = map.get(key); if(!lock.validate(stamp)) { stamp = lock.readLock(); try { return map.get(key); } finally { lock.unlock(stamp); } } return value; }
5.使用條件
該條件類提供了一個線程等待在執行關鍵部分出現一些狀況的能力。
當線程獲得對臨界區的訪問但沒有執行其操作的必要條件時,可能會發生這種情況。例如,讀者線程可以訪問共享隊列的鎖,該隊列仍然沒有任何數據要消耗。
傳統上,Java 為線程互通提供了wait(),notify()和notifyAll()方法。條件有類似的機制,但另外,我們可以指定多個條件:
public class ReentrantLockWithCondition { Stack<String> stack = new Stack<>(); int CAPACITY = 5; ReentrantLock lock = new ReentrantLock(); Condition stackEmptyCondition = lock.newCondition(); Condition stackFullCondition = lock.newCondition(); public void pushToStack(String item){ try { lock.lock(); while(stack.size() == CAPACITY) { stackFullCondition.await(); } stack.push(item); stackEmptyCondition.signalAll(); } finally { lock.unlock(); } } public String popFromStack() { try { lock.lock(); while(stack.size() == 0) { stackEmptyCondition.await(); } return stack.pop(); } finally { stackFullCondition.signalAll(); lock.unlock(); } } }
對比:
Condition提供的方法和Java原生的wait(),notify(),notifyAll()功能類似,但是可以同時創建多個Condition條件,而java原生則不能指定特定的條件。
六,結論
在本文中,我們已經看到了Lock接口和新引入的StampedLock類的不同實現。我們還探討了如何使用Condition類來處理多個條件。
java.util.concurrent.Locks使用指南