6.4 JUC — 可重入鎖

ReentrantLock

ReentrantLock實現了Lock接口,而ReentrantLock其實相當於一副空殼,它的主要功能就是控制構造出公平鎖還是非公平鎖,對鎖的相關操作細節都是由內部類Sync來完成。Sync繼承自AQS(AbstractQueuedSynchronizer),並再衍生出兩個內部類非公平鎖NofairSync和公平鎖FairSync。
通過ReentrantLock的的默認構造函數爲非公平鎖,也可通過含參構造函數來控制其構造公平鎖。

public ReentrantLock() {
        sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

兩種鎖的主要區別就在於lock操作上,FairSync的lock就是直接通過AQS的acquire函數來嘗試獲取同步狀態,但是NofairSync的lock會先嚐試性的直接去獲取同步狀態,如果失敗再通過acquire函數進行獲取。

//公平鎖FairSync的鎖操作
final void lock() {
    acquire(1);
}


//非公平鎖NonfairSync的鎖操作
final void lock() {
    //直接嘗試獲取同步狀態
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

AQS的acquire函數中又會調用子類的tryAcquire函數來嘗試獲取同步狀態,而兩種鎖在tryAcquire函數上又有不同。

//公平鎖FairSync的tryAcquire操作
protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
              //區別在這裏,公平鎖會先判斷同步隊列有沒有其他節點在等待
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
			//在這裏可以看出可重入的思想,如果當前線程就是持有鎖的線程,則直接將狀態state + 1
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }


//非公平鎖nonfairTryAcquire的鎖操作
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
              //非公平鎖不管同步隊列有沒有節點,而是直接嘗試獲取同步狀態
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //在這裏可以看出可重入的思想,如果當前線程就是持有鎖的線程,則直接將狀態state + 1
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

ReentrantReadWriteLock

ReentrantReadWriteLock實現自ReadWriteLock(讀寫鎖)的接口。ReentrantReadWriteLock主要使用在數據的讀取操作遠多於寫操作的情況下,可以提高併發性。它具有如下特徵:

a. 維護了一對鎖ReadLock(讀鎖)和WriteLock(寫鎖)讀鎖在同一時刻允許多個線程持有,而寫鎖在被持有時,其他嘗試獲取讀鎖或者寫鎖的線程都會被阻塞。讀鎖的lock方法會直接調用sync的acquireShared方法,從而調用tryAcquireShared方法:

//讀鎖獲取同步狀態
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    //如果當前寫鎖數量不爲0且獲取到同步狀態的線程不是當前線程,則直接返回-1。根據AQS的流程,此線程會被加入到同步隊列中。
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    //獲取讀鎖數量
    int r = sharedCount(c);
    //按照當前鎖的模式(公平鎖/非公平鎖)判斷線程是否需要阻塞。
    //如果不需要阻塞,且讀數量小於最大數量,嘗試CAS獲取同步狀態
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        //如果讀鎖數量爲0,則當前線程爲第一個讀線程
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } 
        //如果數量不爲0,但是當前線程就是第一個讀線程(說明是重入了),設置第一個讀線程的重入次數。
        else if (firstReader == current) {
            firstReaderHoldCount++;
        } 
        //其他讀線程(非第一個)的重入次數設置。通過在每個線程中的內存空間保存HodlerCount類(用於記錄當前線程獲取鎖的次數),來獲取相應的次數
        else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        //獲取同步狀態成功
        return 1;
    }
    //獲取讀鎖失敗後,進入自旋繼續嘗試
    return fullTryAcquireShared(current);
}

寫鎖的lock方法則會調用sync的tryAcquire方法:

//寫鎖獲取同步狀態
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    //獲取狀態:讀 + 寫
    int c = getState();
    //通過狀態獲取寫數量
    int w = exclusiveCount(c);
    //同步狀態不爲0,說明有線程持有同步狀態
    if (c != 0) {
        //如果寫數量爲0(說明有線程在讀)或者 寫數量不爲0(說明沒有讀線程,只有寫線程)但是寫線程不是當前線程,則返回失敗
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        //來到這裏說明是寫線程重入,則判斷寫數量是否超出最大值,超出拋異常
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        //當前線程成功獲取寫鎖,返回成功
        setState(c + acquires);
        return true;
    }
    /*
	*同步狀態爲0,說明沒有線程在讀寫。則按照當前鎖的模式(公平鎖/非公平鎖)判斷線程是否需要阻塞。
    *如果需要阻塞,或者不需要阻塞但是沒有成功獲取同步狀態,返回失敗
    */
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    //說明成功獲取同步狀態,設置當前線程爲持有鎖線程,並返回成功
    setExclusiveOwnerThread(current);
    return true;
}

b. 還維護了一個Sync,所有的鎖操作都是由Sync完成的,也就是說讀鎖和寫鎖共同使用一個同步隊列和一個同步狀態state(在Sync裏面)。在ReentrantReadWriteLock的同步隊列中,將同步狀態state分成了高16位(讀狀態)和低16位(寫狀態),通過位運算來獲取值。

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

//獲取讀數量,通過將state右移1位得到
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
//獲取寫數量,通過將state和0xFFFF進行與操作來得到
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

寫操作雖然會阻塞其他的寫操作,但由於ReentrantReadWriteLock是可重入的鎖,所以寫數量會在該鎖被重入的時候用於記錄重入次數。

c. Sync內部含有子類NofairSync和FairSync,也就是說ReentrantReadWriteLock也是支持公平鎖和非公平鎖的。

public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
}

ThreadLocal

在ReentrantReadWriteLock的readLock中,通過一個HoldCounter類來記錄當前線程的重入次數,然後將其存儲到readHolds中。而readHolds是在ReentrantReadWriteLock定義的一個內部靜態類ThreadLocalHoldCounter,繼承自ThreadLocal<HoldCounter>。
ThreadLocal是一種線程的本地存儲,它爲變量在每個線程中都創建了一個實例,存儲在共用的ThreadLocalMap中(以線程爲鍵,實例爲值)。ThreadLocal是線程共享的,但裏面存儲的變量卻不是共享的,因此ThreadLocal並不是用來解決多線程變量共享的問題,它只是用來方便用戶存儲和讀取的一種存儲結構。

鎖降級

鎖降級是指把持住當前擁有的寫鎖(進行數據寫入)的同時,再獲取到讀鎖進行數據讀取使用,然後再釋放寫鎖,最後釋放讀鎖。這樣做的目的是保證數據的可見性,防止本線程在數據完成寫操作後,再進行讀取前有其他線程修改了數據,從而讓本線程先獲取到讀鎖以保證其他線程無法加寫鎖,然後本線程再釋放寫鎖,讀取數據。

//這是Oracle官方的示例,體會一下。
class CachedData {
   Object data;
   volatile boolean cacheValid;
   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        try {
          // Recheck state because another thread might have
          // acquired write lock and changed state before we did.
          if (!cacheValid) {
            data = ...
            cacheValid = true;
          }
          // Downgrade by acquiring read lock before releasing write lock
          rwl.readLock().lock();
        } finally {
          rwl.writeLock().unlock(); // Unlock write, still hold read
        }
     }

     try {
       use(data);
     } finally {
       rwl.readLock().unlock();
     }
   }
 }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章