1. 程式人生 > >顯式鎖

顯式鎖

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來提升該方法的程式碼執行速度。

讀寫鎖表示也有兩個鎖,一個是讀操作相關的鎖,也成共享鎖;另一個是寫操作相關的鎖,也叫排他鎖。也就是多個讀鎖之間不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥