ReentrantLock與ReentrantReadWriteLock源碼分析

《Java并發編程之美》讀書筆記

獨占鎖ReentrantLock的原理

類圖結構

ReentrantLock是可重入的獨占鎖,同時只能有一個線程可以獲取到該鎖,其他獲取該鎖的線程會被阻塞返給到AQS阻塞隊里面。


Xnip2019-08-22_12-34-24.jpg

從類圖看到,ReentrantLock最終還是基于AQS來實現的,并且能夠根據參數來決定其內部是一個公平鎖還是非公平鎖,默認是非公平鎖。

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

其中Sync類直接繼承自AQS,它的子類NonfairSync和FairSync分別實現了獲取鎖的非公平策略。
在這里,AQS的狀態值state表示該線程獲取鎖的可重入次數,在默認情況下,state的指表示當前鎖沒有被任何線程持有,當一個線程第一次獲取該鎖時會嘗試使用CAS設置state的狀態值為1,如果CAS成功則當前線程獲取了該鎖,單后記錄該鎖的持有者為當前線程。在該線程沒有釋放鎖的情況下第二次獲取該鎖后,狀態值被設置為2,這就是可重入次數,在該線程釋放該所時,會嘗試使用CAS讓狀態值為1,如果減1后狀態值為0,則當前線程釋放該鎖。

獲取鎖

1.void lock()方法
當一個線程調用該方法時候,說明該線程希望獲取該鎖,如果鎖當前沒有被其他線程占用并且當前線程之前沒有獲取過該鎖,則當前線程會獲取該鎖,然后設置當前鎖的擁有者為當前線程,并且AQS的狀態值為1,然后直接返回,如當前線程之前已經獲取過該鎖,則這次簡單的把AQS的狀態值加1.如果該鎖已經被其他線程所擁有的,則調用該方法的線程會被放入到AQS阻塞隊列阻塞掛起。

 public void lock() {
        sync.acquire(1);
    }

在如上的代碼中,ReentrantLock的lock()委托給了sync類,根據創建的ReentrantLock構造函數選擇實現的是NonfairSync還是FairSync,這個鎖是一個非公平鎖還是公平鎖。這里先看sync子類NonfairSync的情況也就是非公平鎖

非公平鎖

final void lock(){
    //1.CAS設置當前值
    if(compareAndSet(0,1)){
        setExclusiveOwnerThread(Thread.currentThread);
    }else{
    //2.調用AQS的acquire方法
        acquire(1);
    }
}

在代碼1中,因為默認AQS的狀態值為0,所以第一個調用Lock的線程會通過CAS設置狀態值為1,CAS成功則表示當前線程獲取到了鎖,然后setExclusiveOwnerThread設置該鎖的擁有者為當前線程。
如果這時候有其他線程調用lock方法企圖獲取該鎖,CAS會失敗,然后會調用AQS的acquire方法。注意,傳遞參數為1。

 public final void acquire(int arg) {
    //調用ReentrantLock重寫的tryAcquire方法。
        if (!tryAcquire(arg) &&
            //tryAcquire返回false會把當前線程放入AQS阻塞隊列里面
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

AQS并沒有提供可用的tryAcquire方法,tryAcquire方法需要子類自己定制化,所以這里代碼3會調用ReentrantLock自己重寫的tryAcquire方法。

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
         @ReservedStackAccess
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //4當前AQS的狀態值為0
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //5當前線程是否是該鎖的持有者
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }//6
            return false;
        }

首先代碼4會查看當前鎖的狀態是否為0,為0則說明當前鎖空閑,那么就嘗試CAS獲取該鎖,將AQS的狀態值從0設置為1,并設置當前鎖的持有者為當前線程然后但會true,如果當前狀態不為0則說明該鎖已經被某個線程所持有,所以代碼首先判斷當前線程是否為該鎖的持有者,如果是則狀態值加1,然后返回true,這里需要注意,nextc<0代表可重入次數溢出了。如果當前線程不是鎖的持有者則返回false,然后其會被放入AQS阻塞隊列。
介紹完了非公平鎖的實現代碼,再來關注非公平在這里是如何實現的。
首先非公平鎖是說先嘗試獲取鎖的線程并不一定比后嘗試獲取鎖的線程優先獲取鎖
這里假設線程A調用了lock方法執行到nonfairTryAcquire的代碼4,發現當前狀態值不為0則執行代碼5,發現當前線程不是線程的持有者,則執行代碼6返回false,然后線程A被放入AQS阻塞隊列。
這時候線程B也調用了lock()方法執行到nonfairTryAcquire的代碼4方法,發現當前狀態值為0了(假設占有該鎖的其他線程釋放了該鎖),所以通過CAS設置獲取到了該鎖。明明是線程A先請求獲取該鎖的???這就是非公平的體現。
這里的線程B,在獲取鎖之前并沒有查看當前AQS隊列里面是否有比自己更早請求該鎖的線程,而是采用了搶奪策略。

公平鎖

 @ReservedStackAccess
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //7.當前AQS狀態值為0
            if (c == 0) {
            //8.公平策略 
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //9.當前線程是該鎖持有者
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

如上的代碼所示,公平鎖tryAcquire策略與非公平鎖類似,不同之處策略,代碼8在設置CAS之前添加了hasQueuedPredecessors方法,該方法是實現公平性的核心代碼

 public final boolean hasQueuedPredecessors() {
       Node t=tail;
       Node h=head;
       Node s;
       return h!=t&&((s=h.text)==null||s.thread!=Thread.currentThread());
    }

在如上的代碼中,如果當前線程節點有前驅節點則返回true,否則如果當前AQS隊列為空或者當前線程節點是AQS第一個節點則返回false。如果h==t則說明當前AQS隊列為空,直接返回false;如果h!=t并且s==null則說明有一個元素將要作為AQS的第一個節點加入隊列(enq函數的第一個元素入隊列是兩步操作:首先常見一個哨兵頭結點,然后將第一個元素插入哨兵節點后面),那么返回true,如果h!=t和s.thread!=Thread.currentThread()則說明隊列里面的第一個元素不是當前線程,則返回true。

void lockInterruptibly()方法

這個方法與lock()方法類似,不同在于,它對中斷進行響應,就是當前線程在調用該方法時,如果其他線程調用了當前線程的interrupt()方法,則當前線程會拋出中斷異常InterruptedException異常,然后返回。

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    
     public final void acquireInterruptibly(int arg)
            throws InterruptedException {
            //如果當前線程被中斷,則直接拋出異常
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

boolean tryLock()方法

嘗試獲取鎖,如果當前鎖沒有被其他線程持有,則當前線程獲取該鎖并返回true,否則返回false。這方法不會引起線程阻塞

    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
     final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            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;
        }

boolean tryLock(long timeout,TimeUnit unit)

嘗試獲取鎖,與tryLock()的不同之處在于,它設置了超時時間,如果超時時間沒有獲取到了該鎖則返回false

public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

釋放鎖 void unlock()方法

嘗試釋放鎖,如果當前線程持有該鎖,則調用該方法會讓該線程對持有AQS state狀態值減1,如果減去1之后狀態值變為1狀態值為0,則當前線程會釋放該鎖,否則僅僅減1而已。如果當前線程沒有持有該鎖則會拋出IllegalMonitorStateException異常

    public void unlock() {
        sync.release(1);
    }
    @ReservedStackAccess
        protected final boolean tryRelease(int releases) {
        //11.如果不是鎖持有者調用unlock則拋出Unlock異常
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //12.如果當前的可重入次數為0,則從孔該鎖的持有線程
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //13.設置可重入次數為原始值-1
            setState(c);
            return free;
        }

如上代碼所示,如果當前線程不是該鎖持有者則直接拋出異常,否則查看狀態值是否為0,為0則說明當前線程要放棄對該鎖的持有權,則執行代碼12把鎖的持有者為null,如果狀態值不為0,則僅僅讓當前線程對該鎖的可重入次數減1.

使用ReentrantLock實現一個簡單的線程安全的list;

public static class ReentrantLockList {
    //線程不安全的List
    private ArrayList<String> array=new ArrayList<>();
    //獨占鎖
    private volatile ReentrantLock lock=new ReentrantLock();
    //添加元素
    public void add(String e){
        lock.lock();
        try{
            array.add(e);
        }finally {
            lock.unlock();
        }
    }
    //刪除元素
    public void remove(String e){
        lock.lock();
        try{
            array.remove(e);
        }finally {
            lock.unlock();
        }
    }
    //獲取數據
    public String get(int index){
        lock.lock();
        try{
            return array.get(index);
        }finally {
            lock.unlock();
        }
    }
}

如上代碼通過操作array元素前進行加鎖保證了同一時間只有一個線程可以對arry進行修改,但是也只能有一個線程對array元素進行訪問。

如圖所示,假如線程Thread1,Thread2,Thread3同時嘗試獲取獨占鎖ReentrantLock,假如Thread1獲取到了,那么Thread2和Thread3就會被轉換為Node 節點被放入ReentrantLock的AQS阻塞隊列,而后被阻塞掛起。


如圖所示,假設Thread1獲取該鎖了之后調用了對應鎖創建的條件變量1 await()方法,那么Thread1就會釋放獲取到的鎖,然后當前線程就會被轉換為Node節點插入條件變量1的條件隊列,由于Thread1釋放了鎖,鎖以阻塞到AQS隊列里面的Thread2和Thread3就有機會獲取到所=鎖,假如使用的公平策略,那么這時候Thread2會獲取到該鎖,從而從AQS隊列里面一出Thread2對應的Node節點。

讀寫鎖ReentrantReadWriteLock原理

類圖結構

1202638-20180614135056420-1667937320.png

解決線程安全問題其實使用ReentrantLock就可以,但是ReentrantLock是獨占鎖,某時只有一個線程就可以獲取該鎖,而實際上會有寫少讀多的場景,顯然ReentrantLock滿足不了需求,所以ReentrantReadWriteLock應運而生。ReentrantReadWriteLock采用讀寫分離的策略,允許讀個線程可以同時獲取讀鎖。

讀寫鎖的內部維護了一個ReadLock和WriteLock,他們依賴Sync實現具體的功能,而Sync繼承自AQS,并且也提供了公平鎖和非公平鎖的實現。我們知道AQS中值維護了一個state狀態,一個state怎么表示讀和寫兩種狀態呢?ReentrantReadWriteLock巧妙的運用state的高16位表示讀狀態,也就是獲取到讀鎖的次數;使用低16位表示獲取到寫鎖的線程的可重入次數。

 static final int SHARED_SHIFT   = 16;
        //讀鎖(共享鎖)狀態單位指65536
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        //讀鎖(共享鎖)線程最大的個數65535
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        //寫鎖(排它鎖)掩碼,二進制 15個1
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
        //返回讀鎖線程數
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        //返回寫鎖可重入個數
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

寫鎖的獲取與釋放

在ReentrantReadWriteLock中寫鎖使用writeLock來實現

void lock()

寫鎖是一個獨占鎖,只有一個線程可以獲取資源。如果當前沒有線程獲取到讀鎖和寫鎖,則當前線程可以獲取到寫鎖然后返回。如果當前已有線程獲取讀鎖或者寫鎖,則當前請求寫鎖的線程就會被阻塞掛起。另外,寫鎖是可重入鎖,如果當前線程已經獲取了該鎖,再次獲取知識簡單的把可重入次數加1然后直接返回。

    public void lock() {
            sync.acquire(1);
        }
        public final void acquire(int arg) {
        //syn重寫tryAcquire方法
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

非公平寫鎖的lock內部調用了AQS的acquire的方法,其中tryAcquire是ReentrantReadWriteLock內部類sync類重寫的。

protected final boolean tryAcquire(int acquires) {
    
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            //1.說明讀鎖或者寫鎖已經被謀線程獲取
            if (c != 0) {
            //2 w=0說明已經有線程獲取了讀鎖,w!=0表示當前線程不是寫鎖擁有者
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                    //3.說明當前線程獲取了寫鎖,判斷可重入次數
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 4.設置可重入次數+1
                setState(c + acquires);
                return true;
            }
            //5.第一個寫線程獲取寫鎖
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

在代碼1中,如果當前AQS的狀態值不為0則說明當前已經有線程獲取到了讀鎖或者寫鎖。在代碼2中,如果w==0說明狀態值的低16位為0,而AQS狀態值不為0,則說明高16位不為0,這暗示已經有線程獲取到讀鎖,所以直接返回false;
而如果w!=0則說明已有線程獲取了寫鎖,在看當前線程是不是該鎖的持有者,如果過不是就返回false;
執行到代碼3說明當前線程之前已經獲取了讀寫鎖,所以判斷該線程的可重入數是不是超過了最大值,是則拋出異常,否則執行代碼4增加當前線程的可重入次數,然后返回true。
如果AQS的狀態值為0則表示目前沒有線程獲取到讀鎖和寫鎖,所以執行代碼5,搶占式執行CAS嘗試獲取寫鎖,獲取成功則設置當前鎖的持有者為當前線程并返回true,否則返回false;
公平鎖的實現為:

  final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }

這里還是使用hasQueuedPredecessors判斷當前線程節點是否有前驅節點,如果有則當前線程放棄獲取寫鎖的權利,直接返回false;

void lockInterruptibly()

類似于lock()方法,它的不同之處在于,他會對中斷做出響應,也就是當其他線程調用了該線程的interrupt()方法中斷了當前線程時,其會拋出InterruptedException

 public void lockInterruptibly() throws InterruptedException {
            sync.acquireInterruptibly(1);
        }

Boolean trylock()

嘗試獲取寫鎖,如果當前沒有其他線程持有寫鎖或者讀鎖,則當前線程獲取寫鎖會成功,然后返回true,如果有返回false,但是當前線程并不會阻塞。如果當前線程已經持有了讀寫鎖后則簡單增加AQS的狀態值后直接返回true。

 public boolean tryLock() {
            return sync.tryWriteLock();
        }
        final boolean tryWriteLock() {
            Thread current = Thread.currentThread();
            int c = getState();
            if (c != 0) {
                int w = exclusiveCount(c);
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
            }
            if (!compareAndSetState(c, c + 1))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

boolean tryLock(long timeout, TimeUnit unit)

與tryAcquire的不同之處在于,多了超時時間參數,如果嘗試獲取寫鎖失敗則會把當前線程掛起指定的時間,帶到超時時間到后當前線程被激活,如果還是沒有獲取到寫鎖則返回false。另外,該方法會對中斷進行響應,也就是當其他線程調用了該線程的interrupt()方法中斷了當前線程時,其會拋出InterruptedException

 public boolean tryLock(long timeout, TimeUnit unit)
                throws InterruptedException {
            return sync.tryAcquireNanos(1, unit.toNanos(timeout));
        }

void unlock()

嘗試釋放鎖,如果當前線程持有該鎖,調用該方法會讓該線程對該線程持有的AQS狀態值減1,如果減去1后當前狀態值為0則當前線程會釋放該鎖,否則僅僅是減1而已。如果當前線程沒有持有該鎖而調用了這個方法則會拋出IllegalMonitor

    public void unlock() {
            sync.release(1);
        }
        
        
    public final boolean release(int arg) {
        //調用ReentrantReadWriteLock中sync實現的tryRelease方法
        if (tryRelease(arg)) {
            //激活阻塞隊列里面的一個線程
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//LockSupport里面的Unpark方法
            return true;
        }
        return false;
    }
        
        
    protected final boolean tryRelease(int releases) {
        //看是否是寫鎖擁有者調用的unlock
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //獲取可重入值,這里沒有高16位,因為獲取寫鎖時讀鎖的狀態肯定為0
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            //如果寫鎖可重入值為0則釋放鎖,否則只是簡單的更新狀態值。
            if (free)
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }

讀鎖的獲取與釋放

ReentrantReadWriteLock中的讀鎖時使用ReadLock來實現的

void lock()

獲取讀鎖,如果當前沒有其他線程持有寫鎖,則當前線程可以獲取讀鎖,AQS狀態指的高16位的值會增加1,然后方法返回,否則如果其他一個線程持有寫鎖,則當前線程就會被阻塞。

    public void lock() {
            sync.acquireShared(1);
        }
        
        public final void acquireShared(int arg) {
        //調用ReentrantReadWriteLock中sync實現的tryAcquireShared方法。
        if (tryAcquireShared(arg) < 0)
        //調用AQS的doAcquireShared方法
            doAcquireShared(arg);
    }

在如上的代碼中,讀鎖的lock方法調用了AQS的acquireShared方法,在其內部調用了ReentrantReadWriteLock中的sync重寫的tryAcquireShared方法。

    protected final int tryAcquireShared(int unused) {
            //1.獲取當前線程的狀態值
            Thread current = Thread.currentThread();
            int c = getState();
            //2.判斷是否被寫鎖占用
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            //3獲取讀鎖計數
            int r = sharedCount(c);
            //4.嘗試獲取鎖,讀個讀線程只有一個會成功,不成功的會進入fullTryAcquireShared進行重試
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                //5.第一個線程獲取讀鎖
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                //6.如果當前線程是第一個獲取讀鎖的線程
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                //7.記錄最后一個獲取讀鎖的線程獲記錄其他線程讀鎖的可重入數。
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null ||
                        rh.tid != LockSupport.getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            //類似于tryAcquireShared
            return fullTryAcquireShared(current);
        }

如上的代碼首先獲取了當前AQS的狀態值,然后代碼2查看是否有其他線程獲取到了寫鎖,如果是則直接返回-1,而后調用AQS的doAcquireShared方法把當前線程放入AQS阻塞隊列。
如果當前要獲取讀鎖的線程之前已經持有了寫鎖,則也可以獲取讀鎖,但是需要注意,一個線程先獲取了寫鎖,然后獲取了讀鎖處理事情完畢后,要記得把讀鎖和寫鎖一起釋放掉,不能只釋放寫鎖。
否則執行代碼3,得到獲取到的讀鎖的個數,到這里就說明沒有線程獲取到寫鎖,但是可能有線程持有讀鎖,然后執行代碼4,其中非公平鎖的readerShouldBlock實現代碼

        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
         final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null &&
            (s = h.next)  != null &&
            !s.isShared()         &&
            s.thread != null;
    }

如上代碼的作用是,如果隊列里面存在一個元素,則判斷第一個元素是不是正在嘗試獲取寫鎖,若不是,則當前線程判斷當前獲取讀鎖的線程是否已經達到了最大值,最后執行CAS操作將AQS狀態值的高16位加1.
代碼5,6記錄第一個獲取讀鎖的線程并統計該線程獲取讀鎖的可重入數。代碼7cachedHoldCounter記錄最后一個獲取到讀鎖的線程和該線程獲取讀鎖的可重入數,readHolds記錄了當前線程獲取讀鎖的可重入數。
如果readerShouldBlock返回true則代表有線程正在獲取寫鎖,所以執行代碼8,fullTryAcquireShared和tryAcquireShared類似,但是fullTryAcquireShared是通過自選獲取。

void lockInterruptibly()

類似于lock()方法,它的不同之處在于,他會對中斷做出響應,也就是當其他線程調用了該線程的interrupt()方法中斷了當前線程時,其會拋出InterruptedException

boolean tryLock()

嘗試獲取寫鎖,如果當前沒有其他線程持有寫鎖,則當前線程獲取讀鎖會成功,然后返回true,如果有返回false,但是當前線程并不會阻塞。如果當前線程已經持有了讀鎖后則簡單增加AQS的狀態值高16位后直接返回true。

boolean tryLock(long timeout,TimeUnit unit)

與tryLock()的不同之處在于,多了超時時間參數,如果嘗試獲取讀鎖失敗則會把當前線程掛起指定的時間,帶到超時時間到后當前線程被激活,如果還是沒有獲取到讀鎖則返回false。另外,該方法會對中斷進行響應,也就是當其他線程調用了該線程的interrupt()方法中斷了當前線程時,其會拋出InterruptedException

void unlock()

public void unlock() {
            sync.releaseShared(1);
        }

如上代碼具體釋放鎖的操作是委托給Sync類來做的

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    
    protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
        //循環知道自己的讀計數-1,CAS更新成功。
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

如以上代碼所示,在無線循環里面,首先會獲取當前的AQS狀態值并將其保存到變量c,然后變量c被減去一個讀計數單位后使用CAS去更新AQS的狀態值,如果更新成功則查看當前的AQS的狀態值是否為0,為0則說明當前已經沒有讀線程占用該鎖,則tryReleaseShared返回try。然后會調用doReleaseShared方法釋放一個由于獲取寫鎖而被阻塞的線程,如果當前AQS的狀態值不為0,則說明還有其他線程持有了讀鎖,所以tryReleaseShared返回false。如果tryReleaseShared中的CAS更新AQS狀態值失敗,則自旋重試直到成功。

基于ReentrantReadWriteLock實現線程安全的list

之前使用ReentrantLock實現線程安全的list,但是由于ReentrantLock是獨占鎖,所以在讀多寫少的情況下性能很差。

public class ReentrantReadWriteLockList {
    //線程不安全的List
    private ArrayList<String> array=new ArrayList<>();
    //獨占鎖
    private volatile ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
    private final Lock readLock=lock.readLock();
    private final Lock writeLock=lock.writeLock();
    
    //添加元素
    public void add(String e){
        writeLock.lock();
        try{
            array.add(e);
        }finally {
            writeLock.unlock();
        }
    }
    
    //刪除元素
    public void remove(String e){
        writeLock.lock();
        try{
            array.remove(e);
        }finally {
            writeLock.unlock();
        }
    }
    //獲取數據
    public String get(int index){
        readLock.lock();
        try{
            return array.get(index);
        }finally {
            readLock.unlock();
        }
    }
}

以上代碼調用get的時候用的是讀鎖,這樣運行多個讀線程來同時訪問list的元素,這在讀多寫少的情況下性能會更好。


參考資料:
《Java并發編程之美》

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容