java.util.concurrent.locks.ReentrantReadWriteLock讀寫鎖源碼解析

1.什麼是讀寫鎖

在同一時刻可以允許多個讀線程訪問,或者寫線程訪問時,所有的讀線程和其他寫線程均被阻塞的鎖。讀寫鎖一分爲二,分爲一個讀鎖和一個寫鎖,通過分離讀鎖和寫鎖,使得併發性相比一般的排他鎖有提升。

2.讀寫鎖所屬包

package java.util.concurrent.locks;

3.讀寫鎖的繼承與實現關係

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable 

4.讀寫鎖自定義同步器源碼

自定義同步器的實現類

abstract static class Sync extends AbstractQueuedSynchronizer


自定義同步器的關鍵變量和方法

//移位的偏移常量
static final int SHARED_SHIFT   = 16;
		
/* 讀鎖增加的數量級
 * 讀鎖使用高16位
 * 所以讀鎖增加1就相當於增加了2*16
 */
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
		
/* 寫鎖的可重入的最大次數
 * 讀鎖允許的最大數量
 */
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
		
/*
 * 寫鎖的掩碼
 * 寫鎖使用低16位
 * 這個掩碼爲了便於與運算去掉高16位
 */
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
		
/**
 * 保存當前線程持有的重入讀鎖的數目,在讀鎖重入次數爲 0 時移除
 */
private transient ThreadLocalHoldCounter readHolds;
		
/*
 * 最近一個成功獲取讀鎖的線程的計數。
 */
private transient HoldCounter cachedHoldCounter;
		
/* firstReader是一個在鎖空閒的時候將最後一個把共享計數從0改爲1的線程,
 * 並且從那開始還沒有釋放讀鎖。
 * 如果不存在則爲null。
 */
private transient Thread firstReader = null;
		
/*
 * firstReaderHoldCount 是 firstReader 的重入計數。
 */
private transient int firstReaderHoldCount;
		
//當前持有讀鎖的線程數
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

//寫鎖重入次數的計數
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

/**
 * 每個線程讀取次數的計數
 * 緩存在HoldCounter中
 */
static final class HoldCounter {
    int count = 0;
    // Use id, not reference, to avoid garbage retention
    final long tid = Thread.currentThread().getId();
}

/**
 * 初始化HolderCounter
 * 返回每個線程初始化的局部變量的值
 */
static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
     public HoldCounter initialValue() {
          return new HoldCounter();
     }
}
		
Sync() {
      //實例化當前線程持有的重入讀鎖的數目
      readHolds = new ThreadLocalHoldCounter();
      // 確保 readHolds 的內存可見性,利用 volatile 寫的內存語義。
      setState(getState()); 
}
/**
 * 如果當前線程在試圖獲取讀鎖時返回true
 */
abstract boolean readerShouldBlock();

/**
 * 如果當前線程在試圖獲取寫鎖時返回true
 */
abstract boolean writerShouldBlock();

自定義同步器的實現方法

tryRelease() 方法:是否允許它在獨佔模式下釋放鎖資源

/*
 * 是否允許它在獨佔模式下釋放鎖資源
 */
protected final boolean tryRelease(int releases) {
	//如果當前線程不是以獨佔式的方式進行,則拋出異常
        if (!isHeldExclusively())
             throw new IllegalMonitorStateException();
	//計算最新的同步狀態值
        int nextc = getState() - releases;
	//寫鎖重入次數的計數是否爲0,就是線程是否已經處於初始化狀態了
        boolean free = exclusiveCount(nextc) == 0;
	//如果free爲true,說明當前線程已經處於初始化狀態了
        if (free)
	    //設置擁有獨佔訪問權限的線程爲null
            setExclusiveOwnerThread(null);
	//原子方式更新最新同步狀態值
        setState(nextc);
        return free;
}
步驟解析:

(1)先判斷當前線程是不是以獨佔的方式進行,計算最新的同步狀態值

(2)如果是獨佔方式運行的話,那麼通過之前計算的最新的同步狀態值來肯計算寫鎖的重入次數。

(3)如果當前線程處於初始化狀態,設置獨佔訪問權限線程不存在爲null。原子方式更新最新同步狀態值。

tryRelease  流程圖:



tryAcquire() 方法:是否允許它在獨佔模式下獲取同步狀態

//是否允許它在獨佔模式下獲取對象狀態
protected final boolean tryAcquire(int acquires) {
        //獲取當前線程
        Thread current = Thread.currentThread();
	//獲取當前同步狀態
        int c = getState();
	//獲取寫鎖重入的次數
        int w = exclusiveCount(c);
	//如果當前的同步狀態不是初始化狀態
        if (c != 0) {
             //如果寫重入的次數爲0或者當前線程不是擁有獨佔訪問權限的線程,返回false
             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;
         }
	 //如果當前線程嘗試獲取寫鎖或者當前同步狀態值不等於c,則返回false
         if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
             return false;
	 //設置擁有獨佔訪問權限的線程爲當前線程
         setExclusiveOwnerThread(current);
         return true;
}
步驟解析:

(1)如果當前同步狀態不是初始化狀態,那麼原子的方式更新同步狀態值。

(2)如果當前嘗試獲取寫鎖或者同步狀態值不爲最新的同步狀態值,則返回false。(寫鎖是互斥的,已經存在寫鎖了,如果還有線程嘗試獲取寫鎖,會無法保證原子性和一致性的問題)。

(3)設置獨佔訪問權限的線程爲當前線程,返回true。

tryAcquire 流程圖:



tryReleaseShared() 方法:

//是否允許它在共享模式下釋放鎖資源
protected final boolean tryReleaseShared(int unused) {
     //獲取當前的線程
     Thread current = Thread.currentThread();
     //如果當前線程是第一個讀線程
     if (firstReader == current) {
           //如果第一個讀線程的計數爲1的情況
           if (firstReaderHoldCount == 1)
		//設置讀線程爲null
                firstReader = null;
           else
		//否則就將讀線程計數減一
                firstReaderHoldCount--;
      } else {
	   //獲取最近一個成功獲取讀鎖的線程的計數。
           HoldCounter rh = cachedHoldCounter;
	   //如果最近一個成功獲取讀鎖的線程的計數爲null或者線程不是當前線程
           if (rh == null || rh.tid != current.getId())
		 //賦值當前線程持有的讀鎖的數目
                 rh = readHolds.get();
	   //獲取當前線程持有的讀鎖的數目
           int count = rh.count;
	   //如果讀鎖的數目小於等於1,就將當前線程持有的讀鎖移除
           if (count <= 1) {
                readHolds.remove();
                if (count <= 0)
                    throw unmatchedUnlockException();
           }
           --rh.count;
      }
      //死循環
      for (;;) {
	  //原子方式獲取當前同步狀態
          int c = getState();
	  //計算最新的同步狀態值
          int nextc = c - SHARED_UNIT;
	  //如果當前線程的同步狀態值等於c,那麼就將當前線程的同步狀態值更新爲nextc
          if (compareAndSetState(c, nextc))
               //最新同步狀態值爲0,說明共享釋放成功
               return nextc == 0;
      }
}
步驟解析:

(1)首先查看當前線程是不是第一個獲取讀鎖的線程,如果第一個獲取讀鎖的線程計數爲1,那麼就是當前線程,直接將第一個獲取讀鎖的線程設置爲null,否則將計數減1。

(2)如果當前線程不是第一個獲取讀鎖的線程,獲取最近一個成功獲取讀鎖的線程,如果爲null或者不是當前線程,就將重入鎖的次數賦值給最近一個成功獲取讀鎖的線程,然後將重入鎖的次數減1。

(3)死循環+cas方式來設置最新同步狀態。


tryWriteLock() 方法:

/**
  * 是否允許嘗試獲取寫鎖
  */
final boolean tryWriteLock() {
	//獲取當前線程
        Thread current = Thread.currentThread();
	//原子方式獲取同步狀態
        int c = getState();
	//如果當前同步狀態不是初始化狀態
        if (c != 0) {
		//獲取線程寫鎖重入的次數
                int w = exclusiveCount(c);
		//如果寫鎖重入的次數爲0或者獨佔訪問的權限的線程不是當前線程,返回false
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
		//如果寫鎖重入的次數等於了最大次數,拋出異常
                if (w == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
        }
	//如果當前線程的同步狀態不爲c,則返回false
        if (!compareAndSetState(c, c + 1))
                return false;
	//設置擁有獨佔訪問權限的線程爲當前線程
        setExclusiveOwnerThread(current);
        return true;
}
步驟解析:

(1)如果當前同步狀態不是初始化狀態,獲取寫鎖重入的次數,如果爲0或者不是當前線程(寫鎖互斥),就返回false,如果大於最大值,就拋出異常。

(2)如果當前同步狀態不是獲取的同步狀態返回false。

(3)設置擁有獨佔訪問權限的線程爲當前線程。

tryWriteLock 流程圖:



tryReadLock() 方法:是否允許嘗試獲取讀鎖

/**
  * 是否允許嘗試獲取讀鎖
  */
final boolean tryReadLock() {
	//獲取當前線程
        Thread current = Thread.currentThread();
	//死循環
        for (;;) {
	     //原子方式獲取同步狀態
             int c = getState();
	     //如果寫鎖重入的次數不爲0並且擁有獨佔訪問線程的權限不爲當前線程,返回false
             if (exclusiveCount(c) != 0 &&
                  getExclusiveOwnerThread() != current)
                  return false;
	     //獲取讀鎖的線程數量
             int r = sharedCount(c);
	     //如果讀鎖的線程數量等於最大數量,拋出異常
             if (r == MAX_COUNT)
                 throw new Error("Maximum lock count exceeded");
	     //如果當前同步狀態是c,那麼就將當前同步狀態更新爲c+SHARED_UNIT
             if (compareAndSetState(c, c + SHARED_UNIT)) {
		 //如果讀鎖的線程數量爲0
                 if (r == 0) {
		      //設置第一個讀線程爲當前線程
                      firstReader = current;
		      //讀線程持有的計數爲1
                      firstReaderHoldCount = 1;
                 } else if (firstReader == current) {//如果第一個讀線程爲當前線程
		      //第一個讀線程的計數加1
                      firstReaderHoldCount++;
                 } else {
		      //最近一個成功獲取讀鎖的線程的計數器
                      HoldCounter rh = cachedHoldCounter;
		      //如果計數器爲null或者計數器的線程不爲當前線程
                      if (rh == null || rh.tid != current.getId())
			  //最近一個成功獲取讀鎖的線程計數器被設置爲當前讀計數器
                          cachedHoldCounter = rh = readHolds.get();
                      else if (rh.count == 0)//如果讀計數器的計數爲0
			  //設置讀計數器
                          readHolds.set(rh);
		      //讀計數器的計數加1
                      rh.count++;
                 }
                 return true;
             }
        }
}
步驟解析:

(1)如果寫鎖重入的次數不爲0,並且當前線程沒有獨佔訪問權限,返回false。

(2)如果當前同步狀態爲c:如果讀線程的數量爲0,設置第一個讀線程爲當前線程,讀線程的計數爲1,如果第一個讀線程是當前線程,就將計數加1,否則將最近一個成功獲取讀鎖的線程的計數器取出,判斷是否爲null或者是否不爲當前線程,將重新設置最近一個成功獲取讀鎖的線程的計數器,判斷如果計數器的計數值爲0,那麼設置讀計數器。最後將讀計數器加1。

(3)如果當前同步狀態不爲c,那麼就進行死循環。繼續執行(2)。

tryReadLock 流程圖:



5.讀寫鎖中讀鎖源碼

public static class ReadLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -5992448646407690164L;
        private final Sync sync;

        /**
         * 使用實現類構造讀鎖
         */
        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

        /**
         * 獲取讀鎖
         */
        public void lock() {
            sync.acquireShared(1);
        }

        /**
         * 僅當寫入鎖在調用期間未被另一個線程保持時,再獲取讀鎖
		 */
        public  boolean tryLock() {
            return sync.tryReadLock();
        }

		/**
         * 嘗試釋放讀鎖資源
         */
        public  void unlock() {
            sync.releaseShared(1);
        }
}

6.讀寫鎖中寫鎖源碼

public static class WriteLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -4992448646407690164L;
        private final Sync sync;

        /**
         * 使用實現類構造寫鎖
         */
        protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

        /**
         * 獲取寫鎖
         */
        public void lock() {
            sync.acquire(1);
        }

        /**
         * 僅當寫入鎖在調用期間未被另一個線程保持時獲取該鎖。
         */
        public boolean tryLock( ) {
            return sync.tryWriteLock();
        }

        /**
         * 釋放寫鎖
         */
        public void unlock() {
            sync.release(1);
        }
}

7.閱讀總結

(1)ReentrantReadWriteLock的讀寫策略:只要有寫線程在寫,其他線程的寫和讀都是阻塞的。讀線程在讀,其他線程也可以讀,但是如果其他線程獲取了寫鎖,那麼更新的數據對本身以外其他讀鎖的線程是不可見的,所以鎖不可以升級。

(2)ReentrantReadWriteLock的注意事項:當前線程已經持有寫鎖,當前線程可以繼續獲取寫鎖,過程叫寫重入。當前線程也可以釋放寫鎖,再獲取讀鎖,過程叫鎖降級,但是其他的線程是不可以寫讀的。

(3)ReentrantReadWriteLock讀寫設計:將讀寫鎖一分爲二,高16位用於讀操作,低16位用於寫操作。



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