[Java源碼][并發(fā)J.U.C]---解析ReentrantReadWriteLock

前言

本文將分析讀寫鎖ReentrantReadWriteLock的源碼,也會在分析中穿插一些例子來加深對源碼的理解. 本文會如以下順序介紹.
1. 整體架構(gòu)
2. 讀寫狀態(tài)的設(shè)計
3. 寫鎖的源碼分析并以例子加深理解
4. 讀鎖的源碼分析并以例子加深理解
5. 鎖降級
本文源碼下載

整體架構(gòu)

讀寫鎖在讀線程獲得鎖時可以允許多個讀線程同時訪問,但是在寫線程獲得鎖時,所有的讀線程和其他寫線程均被阻塞.

ReentrantReadWriteLock.png

從上圖可以看到

1. ReentrantReadWriteLock主要是實現(xiàn)了接口ReadWriteLock來實現(xiàn)讀寫鎖,并且真正返回的讀鎖是ReadLock的一個實例,寫鎖是WriteLock的一個實例.
2. ReentrantReadWriteLock中有一個Sync的成員變量sync(SyncReentrantReadWriteLock內(nèi)部類), 并且ReadLockWriteLock使用了該成員變量sync來實現(xiàn)它們從接口Lock繼承的抽象方法.
3. Sync的一個實例sync可以是一個FairSync或者NonfairSync, 并且Sync類繼承了AbstractQueuedSynchronizer,由此可知SyncReentrantReadWriteLock類的核心,并且實現(xiàn)了讀寫鎖的具體邏輯.

接下來分析的內(nèi)容都是跟Sync類息息相關(guān).

讀寫狀態(tài)的設(shè)計

先了解一下讀寫狀態(tài)的設(shè)計. 我們知道AQS中有一個狀態(tài)值, 比如在ReentrantLock中表示持有鎖的線程重入了多少次. 但是在ReentrantReadWriteLock中有讀鎖和寫鎖因此需要劃分,所以高16位代表讀鎖的狀態(tài),低16位代表寫鎖的狀態(tài).
如圖所示,一個線程已經(jīng)獲取了寫鎖,并且重進(jìn)入了兩次,同時也連續(xù)獲取了兩次讀鎖.(有人可能會疑惑為什么在獲得寫鎖的同時還可以獲得讀鎖呢, 在鎖降級的時候你會得到答案.)

來自Java并發(fā)編程的藝術(shù).png

從讀寫鎖的作用可知讀鎖是一個共享鎖, 寫鎖是一個互斥鎖. 因此sharedCount(int c)是為了獲取讀鎖的狀態(tài)值, exclusiveCount(int c)是為了獲取寫鎖的狀態(tài)值.

abstract static class Sync extends AbstractQueuedSynchronizer {
        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;

        /** 返回c的高16位  讀狀態(tài)*/
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        /** 返回c的低16位  寫狀態(tài)*/
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
}

寫鎖的獲取和釋放

先分析寫鎖是因為寫鎖與之前分析的鎖在獲取和釋放的過程基本類似,而讀鎖相較于寫鎖會稍微復(fù)雜一點點.

寫鎖的獲取

源碼如下
作用: 當(dāng)前線程嘗試獲取寫鎖, 獲取成功返回true,獲取失敗返回false.

/**
         * 作用: 寫鎖的獲取
         *
         * @param acquires 獲取的個數(shù)
         * @return true表示獲取鎖, false表示未獲取鎖
         */
        protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();   // 整體狀態(tài)
            int w = exclusiveCount(c); // 寫狀態(tài)的個數(shù)
            /**
             *  整體狀態(tài)如果等于0 表明讀鎖和寫鎖目前都沒有線程獲取到 則可以去獲取寫鎖
             *  如果不等于0
             *  1. 存在讀鎖或者當(dāng)前線程不是已經(jīng)獲取寫鎖的線程,則直接返回
             *  2. 如果寫鎖的數(shù)量沒有超過最高值則獲得寫鎖
             */

            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                // 存在讀鎖或者當(dāng)前線程不是已經(jīng)獲取寫鎖的線程
                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;
            }
            /**
             *  表示整體狀態(tài)為0
             *  如果writeShouldBlock需要阻塞或者CAS操作不成功則返回false
             */
            if (writerShouldBlock() ||
                    !compareAndSetState(c, c + acquires))
                return false;
            /**
             * 請注意setExclusiveOwnerThread該方法設(shè)置的是寫鎖
             */
            setExclusiveOwnerThread(current); // 設(shè)置當(dāng)前線程是獲得寫鎖的線程
            return true;
        }

其實獲取寫鎖的邏輯比較簡單. 具體細(xì)節(jié)可以參考上面的注解.
1. 如果不存在讀鎖或?qū)戞i(狀態(tài)為0),則成功獲取鎖并設(shè)置setExclusiveOwnerThread后返回true.
2. 如果存在讀鎖,則直接返回false.
3. 如果存在寫鎖, 分以下兩種情況:

  • 3.1 如果寫鎖的線程不是當(dāng)前線程,則返回false.
  • 3.2 如果寫鎖的重入數(shù)已經(jīng)超過了最大值,則返回false.
  • 3.3 設(shè)置寫鎖的重入數(shù)(加1), 返回true.

流程圖如下:


tryAcquire.png
關(guān)于writeShouldBlock()readShouldBlock()

這兩個方法是Sync的抽象方法, 由子類實現(xiàn), 可以看一下公平鎖和非公平鎖的具體實現(xiàn).

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        // 寫鎖永遠(yuǎn)不需要阻塞
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {
            /* As a heuristic to avoid indefinite writer starvation,
             * block if the thread that momentarily appears to be head
             * of queue, if one exists, is a waiting writer.  This is
             * only a probabilistic effect since a new reader will not
             * block if there is a waiting writer behind other enabled
             * readers that have not yet drained from the queue.
             */
            return apparentlyFirstQueuedIsExclusive();
        }
    }

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
        // 如果前面有節(jié)點 返回true 說明需要阻塞
    }

寫鎖的釋放

作用: 寫鎖的釋放

/**
         *
         * 作用: 寫鎖的釋放
         * @param releases 釋放的個數(shù)
         * @return 寫鎖是否完全釋放 true 完全釋放
         */
        protected final boolean tryRelease(int releases) {
            // 如果當(dāng)前線程不是已經(jīng)獲取寫鎖的線程,則直接拋出異常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            // 判斷寫鎖(重入鎖)是否已經(jīng)全部釋放完
            if (free)
                setExclusiveOwnerThread(null);
            setState(nextc); // 設(shè)置狀態(tài)
            return free;
        }

例子: 測試寫鎖的獲取和釋放

工具類SleepUnitCache

package com.sourcecode.reentrantreadwritelock;

import java.util.HashMap;
import java.util.Map;

public class Cache {
    static Map<String, Object> map = new HashMap<String, Object>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();
    // 獲取一個key對應(yīng)的value
    public static final Object get(String key) {
        r.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " gets lock.");
            SleepUnit.sleep(5);
            return map.get(key);
        } finally {
            System.out.println(Thread.currentThread().getName() + " releases lock.");
            r.unlock();
        }
    }
    // 設(shè)置key對應(yīng)的value刺洒,并返回舊的value
    public static final Object put(String key, Object value) {
        w.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " gets lock.");
            SleepUnit.sleep(10);
            return map.put(key, value);
        } finally {
            System.out.println(Thread.currentThread().getName() + " releases lock.");
            w.unlock();
        }
    }
    // 清空所有的內(nèi)容
    public static final void clear() {
        w.lock();
        try {
            map.clear();
        } finally {
            w.unlock();
        }
    }
}
public class SleepUnit {
    public static void sleep(int time) {
        try {
            TimeUnit.SECONDS.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

測試類. 啟動五個線程去獲取寫鎖,通過打印出AQS中的等待隊列來觀察情況.

public class TestCache {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new Runner(), "thread-" + i).start();
        }
        for (int i = 0; i < 10; i++) {
            SleepUnit.sleep(5);
            Cache.rwl.printWaitingNode();
        }
    }

    static class Runner implements Runnable {
        public void run() {
            Cache.put("k0", "k0");
        }
    }
}

結(jié)果輸出: 結(jié)果并沒有什么意外, 當(dāng)所有線程嘗試去獲取寫鎖時只有一個線程可以拿到鎖.

thread-0 gets lock.
[NULL,-1]->[thread-3,獨占,-1]->[thread-2,獨占,-1]->[thread-1,獨占,-1]->[thread-4,獨占,0]->
[NULL,-1]->[thread-3,獨占,-1]->[thread-2,獨占,-1]->[thread-1,獨占,-1]->[thread-4,獨占,0]->
thread-3 gets lock.
[NULL,-1]->[thread-2,獨占,-1]->[thread-1,獨占,-1]->[thread-4,獨占,0]->
[NULL,0]->thread-2 gets lock.
[thread-2,獨占,-1]->[thread-1,獨占,-1]->[thread-4,獨占,0]->
[NULL,-1]->[thread-1,獨占,-1]->[thread-4,獨占,0]->
thread-1 gets lock.
[NULL,-1]->[thread-4,獨占,0]->
[NULL,-1]->[thread-4,獨占,0]->
thread-4 gets lock.
[NULL,0]->
[NULL,0]->
[NULL,0]->

讀鎖的獲取和釋放

讀鎖相對于寫鎖來說比較復(fù)雜點,因為讀鎖的時候是共享的,意味著每個線程都可以獲得線程, 又因為讀鎖是可重入的,所以每個獲得讀鎖的線程都有一個對應(yīng)的可重入的數(shù)量.說到這里很容易就會想到用ThreadLocal類來實現(xiàn)的. 關(guān)于ThreadLocal可以參考我的另外一篇博客[Java源碼][并發(fā)J.U.C]---解析ThreadLocal, 因此接下來先介紹一下讀鎖用到的一些變量及其作用.

讀鎖使用到的變量介紹

/**
         * 一個ThreadLocal的子類,value值是HoldCounter類的對象并重寫了initValue()方法
         */
        static final class ThreadLocalHoldCounter
                extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }

        /**
         * 一個thredlocal實例保存線程對應(yīng)的HoldCount
         * 在構(gòu)造函數(shù)或者readObject中完成初始化
         * 當(dāng)讀鎖線程的重入數(shù)變?yōu)?時,會被removed.
         */
        private transient ThreadLocalHoldCounter readHolds;

        /**
         * 成功獲取讀鎖的最后一個線程的HoldCounter對象.
         * 為了避免總是去readHolds中查找
         */
        private transient HoldCounter cachedHoldCounter;

        /**
         * firstReader是第一個獲得讀鎖定的線程, 
         * 嚴(yán)格意義上是第一個使得讀鎖狀態(tài)值從0變?yōu)?的線程
         * firstReaderHoldCount是其對應(yīng)的重入數(shù)
         *
         */
        private transient Thread firstReader = null;
        private transient int firstReaderHoldCount;

        /**
         *  構(gòu)造函數(shù), 初始化readHolds并設(shè)置狀態(tài)
         */
        Sync() {
            readHolds = new ThreadLocalHoldCounter();
            setState(getState()); // ensures visibility of readHolds
        }

在讀鎖中主要使用了三個變量來保持讀鎖的獲取和釋放.
1. firstReader 存儲著第一個把整體狀態(tài)從0變?yōu)?code>1的線程.
2. cachedHoldCounter 保存著最后一個獲取讀鎖線程的HoldCounter對象.
3. readHolds保存每個線程和其對應(yīng)的HoldCounter對象, 不包括firstReader, 包括最后一個獲取讀鎖線程.

讀鎖的獲取

tryAcquireShared方法.

/**
         * @param unused 釋放
         * @return  返回一個數(shù)值如果大于等于0,表明獲得鎖.
         *          返回一個數(shù)值如果小于0,表明沒有獲得鎖.
         */
        protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState(); //獲取當(dāng)前狀態(tài)
            if (exclusiveCount(c) != 0 &&
                    getExclusiveOwnerThread() != current)  //如果寫鎖存在并且寫鎖持有者不是當(dāng)前線程
                return -1;
            // 說明 1.寫鎖不存在 或者 2.寫鎖存在但是寫鎖持有者是當(dāng)前線程
            int r = sharedCount(c);  // 獲取讀鎖的個數(shù)

            /**
             *   1. 讀鎖不需要阻塞.
             *   2. 讀鎖的總個數(shù)沒有超過最大數(shù).
             *   3. 通過CAS設(shè)置c的狀態(tài) 因為高16位是讀鎖的個數(shù) 所以需要加上1<<16.
             */
            if (!readerShouldBlock() &&
                    r < MAX_COUNT &&
                    compareAndSetState(c, c + SHARED_UNIT)) {
                /**
                 *  上面三個條件都滿足的情況下會進(jìn)入這里繼續(xù)執(zhí)行
                 *  1.  r == 0 意味著當(dāng)前線程是第一個獲得讀鎖的線程(之前沒有獲得過).
                 *  2.  firstReader == current 意味當(dāng)前線程是那個之前第一個獲得讀鎖的線程 可以重入
                 *  3.  如果都不是就說明當(dāng)前線程不是第一個獲得讀鎖的線程,因此當(dāng)前線程最起碼是第二個獲得讀鎖的線程,
                 *      a.  先去cachedHoldCounter看一下是不是最后一次獲得讀鎖的線程,如果不是就把當(dāng)前線程緩存起來
                 *          (因為此時該線程是目前最后一個獲得讀鎖的線程)
                 *      b.  如果是的話如果rh.count==0,就需要把從readHolds中添加進(jìn)去
                 *            (這是因為在對應(yīng)的release中rh.count==0的時候readHolds做了清除操作)
                 *      rh.count++
                 *  返回1,代表成功獲得鎖.
                 */
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } 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);
        }

作用: 嘗試獲取讀鎖, 返回大于等于0的數(shù)表明獲得鎖, 返回小于0的數(shù)表明未獲得鎖.
邏輯如下:
1. 如果另一個線程持有寫鎖定,則失敗.
2. 如果進(jìn)入到這里表明兩種情況寫鎖不存在或者寫鎖存在但是寫鎖持有者就是當(dāng)前線程
3. 如果同時滿足3個條件分別是a.讀鎖不需要阻塞b.讀鎖的總個數(shù)沒有超過最大數(shù)c.CAS設(shè)置狀態(tài)成功, 則當(dāng)前線程成功獲得讀鎖. 否則進(jìn)入fullTryAcquireShared方法(放到后面分析).
4. 滿足了上面3個條件后需要更新讀鎖的相關(guān)變量.基本邏輯如下:

  • 4.1 如果讀鎖狀態(tài)值為0,表明該線程是第一個獲得讀鎖的線程,設(shè)置firstReaderfirstReadHoldCount變量.
  • 4.2 如果不是4.1則表明讀鎖已經(jīng)被線程獲取過了,那么如果當(dāng)前線程就是那個第一次獲得讀鎖的線程,則設(shè)置其重入數(shù)firstReaderHoldCount即可.
  • 4.3 如果不是4.2則表明讀鎖已經(jīng)被獲取過了并且當(dāng)前線程并不是那個第一次獲得讀鎖的線程,此時就可以去緩存cachedHoldCounter中看看是不是當(dāng)前線程,如果不是的話就從readHolds中獲取并將其緩存在cachedHoldCounter中. 最后rh.count++設(shè)置一下重入數(shù).
    注意 當(dāng)某個HoldCountercount為0的時候,readHolds是會將其清除掉的.

流程圖如下:

tryAcquireShared.png

fullTryAcquireShared方法

/**
         * 作用: 獲取鎖, 返回值大于等于0表示
         * 用于處理tryAcquireShared方法中未能滿足的3個條件
         */
        final int fullTryAcquireShared(Thread current) {
            HoldCounter rh = null;
            for (;;) {
                int c = getState();                 // 獲取當(dāng)前狀態(tài)
                if (exclusiveCount(c) != 0) {       // 如果寫鎖不為0 表明存在寫鎖
                    // 如果寫鎖不是當(dāng)前線程(說明此刻已經(jīng)有別的線程獲得寫鎖了),則需要阻塞當(dāng)前線程所以返回-1.
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                    // else we hold the exclusive lock; blocking here
                    // would cause deadlock.
                    /**
                     *  這一段話的意思是如果當(dāng)前線程如果在這里block了,那會形成死鎖,
                     *  因為當(dāng)前線程已經(jīng)在持有寫鎖的情況來請求讀鎖的,那么該鎖在沒有釋放鎖的情況下block了
                     *  就會形成死鎖了
                     */
                } else if (readerShouldBlock()) {  // 不存在寫鎖并且需要阻塞
                    // Make sure we're not acquiring read lock reentrantly
                    /**
                     * 確認(rèn)一下當(dāng)前線程有沒有在之前獲得鎖,也就是在阻塞前確認(rèn)一下不是重入讀鎖的線程
                     * 如果是重入鎖的話就讓他操作CAS 如果不是的話就需要阻塞
                     * 至于為什么,我個人理解如下:
                     *    對公平鎖來說,readShouldBlock()返回true,表明AQS隊列中有等待寫鎖的線程,
                     *    那么如果重入讀鎖也返回-1讓其阻塞的話那就會形成死鎖,因為該重入讀鎖由于阻塞無法釋放讀鎖躲叼,
                     *    AQS等待隊列中的寫鎖又因為讀鎖的存在而無法獲得寫鎖從而形成死鎖了.
                     */
                    
                    if (firstReader == current) {  // 當(dāng)前線程已經(jīng)獲得過鎖則
                        // assert firstReaderHoldCount > 0;
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current)) {
                                rh = readHolds.get();
                                if (rh.count == 0)  // 計數(shù)為0, 需要從readHolds中刪除
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0) //說明當(dāng)前線程之前沒有獲得鎖
                            return -1;
                    }
                }
                // 如果讀鎖的個數(shù)達(dá)到最大值拋出error
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // CAS操作 邏輯跟tryAcquireShared方法里面的類似.
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        firstReaderHoldCount++;
                    } else {
                        if (rh == null)
                            rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                        cachedHoldCounter = rh; // cache for release
                    }
                    return 1;
                }
            }
        }

fullTryAcquireShared是為了處理tryAcquireShared中無法同時滿足那三個條件而進(jìn)行的處理方法, for循環(huán)操作.
1. 如果存在寫鎖并且寫鎖不是當(dāng)前線程則需要阻塞當(dāng)前線程返回-1.
2. 如果存在寫鎖并且寫鎖就是當(dāng)前線程則不需要管readerShouldBlock()方法進(jìn)行CAS操作.
3. 如果不存在寫鎖,如果當(dāng)前線程之前獲得過讀鎖則進(jìn)行CAS操作,否則返回-1,阻塞當(dāng)前線程.
4. 如果讀鎖的個數(shù)達(dá)到最大值則拋出Error.
5. 進(jìn)行CAS操作.

對于第3點的個人理解

對公平鎖來說,readShouldBlock()返回true,表明AQS隊列中有等待寫鎖的線程,那么如果重入讀鎖也返回-1讓其阻塞的話那就會形成死鎖,因為該重入讀鎖由于阻塞無法釋放讀鎖,AQS等待隊列中的寫鎖又因為讀鎖的存在而無法獲得寫鎖從而形成死鎖了.

對應(yīng)流程圖如下:

fullTryAcquireShared.png

讀鎖的釋放

/**
         *  作用: 釋放讀鎖
         * @param unused
         * @return
         */
        protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread(); // 獲取當(dāng)前線程

            /**
             *  1. firstReader == current 表明當(dāng)前線程是那個第一個獲得讀鎖的線程,可以直接操作firstReaderHolderCount就可以了
             *  2. 如果不是則看是不是最后一次獲得讀鎖的線程,
             *      a. 如果不是則取出當(dāng)前線程對應(yīng)的holdcount,保存到rh中
             *      b. 如果是直接保存到rh
             *
             *      如果rh的count為1,表明當(dāng)前線程獲得讀鎖后沒有重入過,既然是釋放鎖,這個時候就需要從threadlocal中刪除掉
             *
             *      rh.count--
             *
             */
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                HoldCounter rh = cachedHoldCounter;
                // 不是最后一個獲得讀鎖的線程,需要從threadlocal中也就是readHolds中取出當(dāng)前線程的HoldCount
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                // count <= 1 需要從readHolds中刪除, 進(jìn)一步如果count<=0表明錯誤
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                // 無論怎么樣 rh已經(jīng)是當(dāng)前線程對應(yīng)的HoldCount, 釋放一個就是減少一個
                --rh.count;
            }
            // 循環(huán)操作更新狀態(tài), 如果讀鎖的個數(shù)為0,則表明所有讀鎖都釋放完畢這個時候返回true.
            // 不然其他情況都是返回false.
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

作用: 釋放讀鎖,返回true如果所有的讀鎖釋放完,否則返回false.
邏輯很簡單,可以直接看代碼注解.

鎖降級

鎖降級: 鎖降級指的是寫鎖降級成為讀鎖凿蒜。如果當(dāng)前線程擁有寫鎖,然后將其釋放,最后再獲取讀鎖,這種分段完成的過程不能稱之為鎖降級.鎖降級是指把持住(當(dāng)前擁有的)寫鎖,再獲取到讀鎖,隨后釋放(先前擁有的)寫鎖的過程.

其實讀鎖這部分已經(jīng)分析到了鎖降級的內(nèi)容了, 在上面的分析中我們已經(jīng)看到在當(dāng)前線程獲取讀鎖的過程中如果存在寫鎖并且該寫鎖就是當(dāng)前線程的時候可以去獲得讀鎖.

必要性: 主要是為了保證數(shù)據(jù)的可見性,如果當(dāng)前線程不獲取讀鎖而是直接釋放寫鎖(接著不加鎖直接讀取數(shù)據(jù)),假設(shè)此刻另一個線程(記作線程T)獲取了寫鎖并修改了數(shù)據(jù),那么當(dāng)前線程無法感知線程T的數(shù)據(jù)更新. 如果當(dāng)前線程獲取讀鎖,即遵循鎖降級的步驟,則線程T將會被阻塞,直到當(dāng)前線程使用數(shù)據(jù)并釋放讀鎖之后,線程T才能獲取寫鎖進(jìn)行數(shù)據(jù)更新。

Sync類中一些其余的方法

/**
         * 嘗試獲取寫鎖,該方法給tryLock調(diào)用,返回false該線程也不會阻塞
         */
        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;
        }

        /**
         * 嘗試獲取讀鎖,該方法給tryLock調(diào)用,返回false該線程也不會阻塞
         */
        final boolean tryReadLock() {
            Thread current = Thread.currentThread();
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0 &&
                        getExclusiveOwnerThread() != current)
                    return false;
                int r = sharedCount(c);
                if (r == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    if (r == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        firstReaderHoldCount++;
                    } 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 true;
                }
            }
        }

ReadLock 和 WriteLock

代碼就不貼了,因為都是調(diào)用的Sync類的方法.

參考

1. Java并發(fā)編程的藝術(shù)
2. http://www.reibang.com/p/6923c126e762
3. http://www.reibang.com/p/f81925c8835a

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子虏等,更是在濱河造成了極大的恐慌民镜,老刑警劉巖啡专,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異制圈,居然都是意外死亡们童,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門鲸鹦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來慧库,“玉大人,你說我怎么就攤上這事馋嗜∑氚澹” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長甘磨。 經(jīng)常有香客問我橡羞,道長,這世上最難降的妖魔是什么济舆? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任卿泽,我火速辦了婚禮,結(jié)果婚禮上滋觉,老公的妹妹穿的比我還像新娘签夭。我一直安慰自己,他們只是感情好椎侠,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布覆致。 她就那樣靜靜地躺著,像睡著了一般肺蔚。 火紅的嫁衣襯著肌膚如雪煌妈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天宣羊,我揣著相機(jī)與錄音璧诵,去河邊找鬼。 笑死仇冯,一個胖子當(dāng)著我的面吹牛之宿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播苛坚,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼比被,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了泼舱?” 一聲冷哼從身側(cè)響起等缀,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎娇昙,沒想到半個月后尺迂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡冒掌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年噪裕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片股毫。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡膳音,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出铃诬,到底是詐尸還是另有隱情祭陷,我是刑警寧澤苍凛,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站颗胡,受9級特大地震影響毫深,放射性物質(zhì)發(fā)生泄漏吩坝。R本人自食惡果不足惜毒姨,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钉寝。 院中可真熱鬧弧呐,春花似錦、人聲如沸嵌纲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逮走。三九已至鸠蚪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間师溅,已是汗流浹背茅信。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留墓臭,地道東北人蘸鲸。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像窿锉,于是被迫代替她去往敵國和親酌摇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容