顯式鎖
Lock與ReentrantLock
與內建加鎖機制不同的是,Lock提供了一種無條件的、可輪詢的、定時的以及可中斷的鎖獲取操作,所有加鎖的方法都是顯式的。Lock介面程式碼如下:
public interface Lock{
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
ReentrantLock實現Lock介面,並提供令人與synchronized相同的互斥性和記憶體可見性。與synchronized相比,它還為處理鎖的不可用性問題提供了更高的靈活性。
以下程式碼給出了Lock介面的標準使用形式,這種形式比使用內建鎖複雜一些,必須在finally塊中釋放鎖。否則,如果在被保護的程式碼塊中丟擲了異常,那麼這個鎖永遠都無法釋放。當使用加鎖時,還須考慮在try塊中丟擲異常的情況,如果可能使物件處於某種不一致的狀態,那麼就需要更多的try-catch或try-finally程式碼塊。
Lock lock = new ReentrantLock();
...
lock.lock();
try{
//更新物件狀態
//捕獲異常,並在必要時恢復不變性條件
}finally{
lock.unlock();
}
輪詢鎖與定時鎖
可定時的與可輪詢的鎖獲取模式是由tryLock方法實現的,與無條件的鎖獲取模式相比,它具有更完善的錯誤恢復機制。在內建鎖中,死鎖是一個嚴重的問題,恢復程式的唯一方法是重新啟動程式,而防止死鎖的唯一方法就是在構造程式時避免出現不一致的鎖順序。可定時與可輪詢的鎖提供了另一種選擇:避免死鎖的發生。
以下程式碼通過tryLock來獲取兩個鎖,如果不能同時獲得,那麼就回退並重新嘗試。
while(true){
if(fromAcct.lock.tryLock()){
try{
if(toAcct.lock.tryLock(){
try{
...
}finally{
toAcct.lock.unLock();
}
}
}finally{
fromAcct.lock.unlock();
}
}
}
Condition 物件
Condition是一種廣義的內建條件佇列,程式碼如下:
public interface Condition{
void await() throws InterruptedExcaption;
boolean await(long time, TimeUnit unit) throws InterruptedException;
long awaitNanos(long nanosTimeout) throws InterruptedException;
void awaitUninterruptibly();
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
每個內建鎖都只能有一個相關聯的條件佇列,因而在想BoundedBuffer這種類中,多個執行緒可能在同一個條件佇列上等待不同的條件謂詞,並且在最常見的加鎖模式下公開條件佇列物件。這些因素都使得無法滿足notifyAll時所有等待執行緒為同一型別的需求。如果想編寫一個帶有多個條件謂詞的併發物件,或者想獲得出條件佇列可見性之外的更多控制權,就可以使用顯式的Lock和Condition而不是內建鎖和條件佇列,這是一種更靈活的選擇。
以下程式碼給出了有界快取的另一種實現,即使用兩個Condition,分別為notFull和notEmpty,用於表示“非滿”與“非空”兩個條件謂詞,程式碼如下:
package beidao.multithread.demo;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionBoundedBuffer<T> {
private static final int BUFFER_SIZE = 100;
protected final Lock lock = new ReentrantLock();
//條件謂詞:notFull(count < items.length)
private final Condition notFull = lock.newCondition();
//條件謂詞:notEmpty(count > 0)
private final Condition notEmpty = lock.newCondition();
@SuppressWarnings("unchecked")
private final T[] items = (T[])new Object[BUFFER_SIZE];
private int tail, head, count;
//阻塞,直到:notFull
public void put(T x) throws InterruptedException{
lock.lock();
try{
while(count == items.length){
notFull.await();
}
items[tail] = x;
if(++tail == items.length){
tail = 0;
}
++count;
notEmpty.signal();
}finally{
lock.unlock();
}
}
//阻塞,直到:notEmpty
public T take() throws InterruptedException{
lock.lock();
try{
while(count == 0)
notEmpty.await();
T x = items[head];
items[head] = null;
if(++head == items.length)
head = 0;
--count;
notFull.signal();
return x;
}finally {
lock.unlock();
}
}
}
當使用顯式的Lock和Condition時,也必須滿足鎖、條件謂詞和條件變數之間的三元關係。在條件謂詞中包含的變數必須由Lock來保護,並且在檢查條件謂詞以及呼叫await和signal時,必須持有Lock物件。
ReentrantLock的常用方法
- int getHoldCount():查詢當前執行緒保持此鎖定的個數,也就是呼叫lock()方法的次數。
- int getQueueLength():返回正等待獲取此鎖定的執行緒估計數。
- int getWaitQueueLength(Condition):返回等待與此鎖定相關的給定條件Condition的執行緒估計數。
- boolean hasQueuedThread(Thread):查詢指定的執行緒是否正在等待獲取此鎖定。
- boolean hasQueuedThreads():查詢是否有執行緒正在等待獲取此鎖定。
- boolean hasWaiters(Condition):查詢是否有執行緒正在等待與此鎖定有關的condition條件。
- boolean isFair():判斷是不是公平鎖,預設情況下ReentrantLock是非公平鎖。
- boolean isHeldByCurrentThread():查詢當前執行緒是否保持此鎖定。
- boolean isLocked():查詢此鎖定是否由任意執行緒保持。
- void lockInterruptibly():如果當前執行緒未被中斷,則獲取鎖定,如果已經被中斷則出現異常。
- boolean tryLock():僅在呼叫時鎖定未被另一個執行緒保持的情況下才獲得該鎖定。
- boolean tryLock(long timeout,TimeUnit unit):如果鎖定在給定等待時間內沒有被另一個執行緒保持,且當前執行緒未被中斷,則獲取該鎖定。
# ReentrantReadWriteLock
類ReentrantLock具有完全互斥排他的效果,即同一時間只有一個執行緒在執行ReentrantLock.lock()方法後面的任務。而JDK提供了一種讀寫鎖ReentrantReadWriteLock類,使用它可以加快執行效率,在某些不需要操作例項變數的方法中,完全可以使用讀寫鎖ReentrantReadWriteLock來提升該方法的程式碼執行速度。
讀寫鎖表示也有兩個鎖,一個是讀操作相關的鎖,也成共享鎖;另一個是寫操作相關的鎖,也叫排他鎖。也就是多個讀鎖之間不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥。