JDK源碼——java.util.concurrent(四)

測試代碼:
https://github.com/kevindai007/springboot_houseSearch/tree/master/src/test/java/com/kevindai/juc

ReadWriteLock

先看看ReadWriteLock的用法和特點

public class ReadWriteLockTest {
    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws InterruptedException {
        ReadWriteLockObject data = new ReadWriteLockObject();
        ReadWriteLockThread r1 = new ReadWriteLockThread(data,false);
        ReadWriteLockThread r2 = new ReadWriteLockThread(data,false);
        ReadWriteLockThread r3 = new ReadWriteLockThread(data,true);

        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        Thread t3 = new Thread(r3);
        t1.start();
        t2.start();
        t3.start();
    }


    static class ReadWriteLockThread implements Runnable{
        boolean flag;
        ReadWriteLockObject object;
        public ReadWriteLockThread(ReadWriteLockObject object,boolean flag){
            this.object = object;
            this.flag = flag;
        }
        @Override
        public void run() {
            if(flag){
                try {
                    object.set();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                try {
                    object.get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class ReadWriteLockObject{
        private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        private Lock readLock = readWriteLock.readLock();
        private Lock writeLocck = readWriteLock.writeLock();

        public void set() throws InterruptedException {
            writeLocck.lock();
            System.out.println("begin set" + sdf.format(new Date()));
            TimeUnit.SECONDS.sleep(5);
            System.out.println("end set" + sdf.format(new Date()));
            writeLocck.unlock();
        }

        public void get() throws InterruptedException {
            readLock.lock();
            System.out.println("begin get" + sdf.format(new Date()));
            TimeUnit.SECONDS.sleep(5);
            System.out.println("end get" + sdf.format(new Date()));
            readLock.unlock();
        }
    }
}

通過這個demo可以看出,ReadWriteLock的讀鎖可以同時運行,不會阻塞;寫鎖會阻塞.

ReadWriteLock的實現是ReentrantReadWriteLock,也是通過AQS來實現的,ReentrantReadWriteLock中有一個Sync類,Sync繼承自AQS,讀鎖、寫鎖均是基於此實現的.

ReentrantReadWriteLock最關鍵的就是Sync類;咱們前面說過AQS中有一個狀態變量state來表示鎖狀態,但讀鎖、寫鎖如何用一個變量表示呢?這裏不得不佩服前輩們的天才,他們用state的前16位表示讀鎖,用後16位表示寫鎖,所以無論是讀鎖還是寫鎖最多隻能被持有2^16次.在判斷讀鎖和寫鎖的時候,需要進行位運算:

  1. 由於讀寫鎖共享同一個狀態變量,所以狀態不爲0,只能說明是有鎖,可能是讀鎖,也可能是寫鎖
  2. 讀鎖是高16爲表示的,所以讀鎖加1,就是狀態的高16位加1,低16位不變,所以要加的不是1,而是2^16,減一同樣是這樣。
  3. 寫鎖用低16位表示,要獲得寫鎖的次數,要用狀態&2^16-1(16個1),結果的高16位全爲0,低16位就是寫鎖被持有的次數。

咱們來看看Sync的代碼

abstract static class Sync extends AbstractQueuedSynchronizer {  
       private static final long serialVersionUID = 6317671515068378041L;  

       //最多支持65535個寫鎖和65535個讀鎖;低16位表示寫鎖計數;高16位表示持有讀鎖的線程數  
       static final int SHARED_SHIFT   = 16;  
       //由於讀鎖用高位部分,讀鎖個數加1,其實是狀態值加 2^16  
       static final int SHARED_UNIT    = (1 << SHARED_SHIFT);  
       static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;  
       /**寫鎖的掩碼,用於狀態的低16位有效值 */  
       static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;  
       /** 讀鎖計數,當前持有讀鎖的線程數,c的高16位 */  
       static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }  
       /** 寫鎖的計數,也就是它的重入次數,c的低16位*/  
       static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }  

       /** 
        * 每個線程持有讀鎖的計數 
        */  
       static final class HoldCounter {  
           int count = 0;  
           //使用id而不是引用是爲了避免保留垃圾。注意這是個常量。  
           final long tid = Thread.currentThread().getId();  
       }  

       /** 
        * 採用繼承是爲了重寫 initialValue 方法,這樣就不用進行這樣的處理: 
        * 如果ThreadLocal沒有當前線程的計數,則new一個,再放進ThreadLocal裏。 
        * 可以直接調用 get。 
        * */  
       static final class ThreadLocalHoldCounter  
           extends ThreadLocal<HoldCounter> {  
           public HoldCounter initialValue() {  
               return new HoldCounter();  
           }  
       }  

       /** 
        * 當前線程持有的可重入讀鎖的數量,僅在構造方法和readObject(反序列化) 
        * 時被初始化,當持有鎖的數量爲0時,移除此對象。 
        */  
       private transient ThreadLocalHoldCounter readHolds;  

       /** 
        * 最近一個成功獲取讀鎖的線程的計數。這省卻了ThreadLocal查找, 
        * 通常情況下,下一個釋放線程是最後一個獲取線程。這不是 volatile 的, 
        * 因爲它僅用於試探的,線程進行緩存也是可以的 
        * (因爲判斷是否是當前線程是通過線程id來比較的)。 
        */  
       private transient HoldCounter cachedHoldCounter;  

       /**firstReader是第一個獲得讀鎖的線程; 
        * firstReaderHoldCount是firstReader的重入計數; 
        * 更準確的說,firstReader是最後一個把共享計數從0改爲1,並且還沒有釋放鎖。 
        * 如果沒有這樣的線程,firstReader爲null; 
        * firstReader不會導致垃圾堆積,因爲在tryReleaseShared中將它置空了,除非 
        * 線程異常終止,沒有釋放讀鎖。 
        *  
        * 跟蹤無競爭的讀鎖計數時,代價很低 
        */  
       private transient Thread firstReader = null;  
       private transient int firstReaderHoldCount;  

       Sync() {  
           readHolds = new ThreadLocalHoldCounter();  
           setState(getState()); // ensures visibility of readHolds  
       }

它的兩個子類,FairSync和NonFairSync比較簡單,它們就是決定在某些情況下讀鎖或者寫鎖是否需要阻塞,通過兩個方法的返回值決定:

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
    }

    /**
     * Fair version of Sync
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }


讀鎖是共享鎖,同一時刻可以被多個線程獲得,下面是獲得讀鎖的代碼:

/** 
 * 獲取讀鎖,如果寫鎖不是由其他線程持有,則獲取並立即返回; 
 * 如果寫鎖被其他線程持有,阻塞,直到讀鎖被獲得。 
 */  
public void lock() {  
    sync.acquireShared(1);  
}  

acquireShared()方法位於AQS中,代碼如下

/** 
 * 以共享模式獲取對象
 */  
public final void acquireShared(int arg) {  
    if (tryAcquireShared(arg) < 0)  
        doAcquireShared(arg);  
}  

其中tryAcquireShared()方法又位於AQS的實現Sync中

protected final int tryAcquireShared(int unused) {  
            Thread current = Thread.currentThread();  
            int c = getState();  
            //持有寫鎖的線程可以獲得讀鎖  
            if (exclusiveCount(c) != 0 &&  
                getExclusiveOwnerThread() != current)  
                return -1;//寫鎖被佔用,且不是由當前線程持有,返回-1  
            //執行到這裏表明:寫鎖可用,或者寫鎖由當前線程持有  
            //獲得讀鎖的數量  
            int r = sharedCount(c);  

            /** 如果不用阻塞,且沒有溢出,則使用CAS修改狀態,並且修改成功 */  
            if (!readerShouldBlock() &&  
                r < MAX_COUNT &&  
                compareAndSetState(c, c + SHARED_UNIT)) {//修改高16位的狀態,所以要加上2^16  
                //這是第一個佔有讀鎖的線程,設置firstReader  
                if (r == 0) {  
                    firstReader = current;  
                    firstReaderHoldCount = 1;  
                } else if (firstReader == current) {//重入計數加1  
                    firstReaderHoldCount++;  
                } else {  
                    // 非 firstReader 讀鎖重入計數更新  
                    //將cachedHoldCounter設置爲當前線程  
                    HoldCounter rh = cachedHoldCounter;  
                    if (rh == null || rh.tid != current.getId())  
                        cachedHoldCounter = rh = readHolds.get();  
                    else if (rh.count == 0)  
                        readHolds.set(rh);  
                    rh.count++;  
                }  
                return 1;  
            }  
            //獲取讀鎖失敗,放到循環裏重試  
            return fullTryAcquireShared(current);  
        }  

重點關注Sync中的tryAcquireShared(),注意,在所有的讀寫鎖中,獲取鎖和釋放鎖每次都是一個計數行爲,鎖其計數都是1,而在獲得讀鎖的過程中,參數根本就沒有意義.上面的代碼包含的邏輯:
1、如果當前寫鎖被其他線程持有,則獲取讀鎖失敗;
2、寫鎖空閒,或者寫鎖被當前線程持有(寫鎖可降級爲讀鎖),在公平策略下,它可能需要阻塞,那麼tryAcquireShared()就可能失敗,則需要進入隊列等待;如果是非公平策略,會嘗試獲取鎖,使用CAS修改狀態,修改成功,則獲得讀鎖,否則也會進入同步隊列等待;
3、進入同步隊列後,就是由AQS來完成喚醒。

下面咱們看一看讀鎖的釋放過程,咱們直接看Sync的實現:

 protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            /** 
            * 當前線程是第一個獲取到鎖的,如果此線程要釋放鎖了(firstReaderHoldCount==1),則firstReader置空 
            * 否則,將線程持有的鎖計數減1 
            */
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                HoldCounter rh = cachedHoldCounter;
                //如果最近一個讀取鎖的線程爲空,後者不是當前線程
                if (rh == null || rh.tid != current.getId())
                    //獲取當前線程持有的鎖的數量
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            //cas+自旋更改狀態(可能有其他的線程也在釋放讀鎖)
            for (;;) {
                int c = getState();
                //高16位-1
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc)
                    return nextc == 0;
            }
        }

釋放讀鎖很簡單,就是把狀態的高16位減1,同時把當前線程持有鎖的計數減1。在釋放的過程中,其他線程可能也在釋放讀鎖,所以修改狀態有可能失敗,因此利用cas+自旋,直到成功爲止.

下面看看寫鎖的獲取,一樣咱們直接看Sync中的實現:

        protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();
            //獲取寫鎖被持有的次數,通過與低16位做與操作得到
            int w = exclusiveCount(c);
            if (c != 0) {
                // c!=0,w==0,說明讀鎖存在  
                //w != 0 && current != getExclusiveOwnerThread() 表示其他線程獲取了寫鎖
                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;
            }


           //執行到這裏,說明不存在任何鎖  
           //WriterShouldBlock留給子類實現公平策略  
           //使用CAS修改狀態 
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;

            setExclusiveOwnerThread(current);
            return true;
        }

主要邏輯:
(1)首先獲得鎖狀態,保存到c中,獲得寫鎖的計數保存到w中;這個時候需要根據c的值來判斷是否存在鎖
(2)如果c!=0,說明存在鎖,如果w==0,說明存在讀鎖,獲取寫鎖不能成功;如果w!=0,但是寫鎖是由其他線程持有的,那麼當前線程獲取寫鎖也不能成功;只有c!=0,w!=0,且寫鎖是由當前線程持有的,才能獲得成功
(3)如果c==0,說明不存在鎖,如果是公平策略,還需要進入同步隊列;如果是非公平策略,會嘗試獲得寫鎖

下面看看寫鎖的釋放,一樣直接看Sycn中的實現

protected final boolean tryRelease(int releases) {  
            if (!isHeldExclusively())  
                throw new IllegalMonitorStateException();  
            int nextc = getState() - releases;  
            boolean free = exclusiveCount(nextc) == 0;  
            //如果鎖是可用的  
            if (free)  
                setExclusiveOwnerThread(null);  
            setState(nextc);  
            return free;  
        } 

釋放寫鎖很簡單,就是狀態的低16位減1,如果爲0,說明寫鎖可用,返回true,如果不爲0,說明當前線程仍然持有寫鎖,返回false;

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