1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Java 重入鎖和讀寫鎖

        共 12629字,需瀏覽 26分鐘

         ·

        2021-03-14 18:19

        點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

        優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          作者 |  低吟不作語

        來源 |  urlify.cn/NRRbiy

        1、重入鎖

        重入鎖 ReentrantLock,顧名思義,就是支持重進(jìn)入的鎖,它表示該鎖能夠支持一個(gè)線程對(duì)資源的重復(fù)加鎖。除此之外,該鎖還支持獲取鎖時(shí)的公平和非公平性選擇

        所謂不支持重進(jìn)入,可以考慮如下場(chǎng)景:當(dāng)一個(gè)線程調(diào)用 lock() 方法獲取鎖之后,如果再次調(diào)用 lock() 方法,則該線程將會(huì)被自己阻塞,原因是在調(diào)用 tryAcquire(int acquires) 方法時(shí)會(huì)返回 false,從而導(dǎo)致線程阻塞

        synchronize 關(guān)鍵字隱式的支持重進(jìn)入,比如一個(gè) synchronize 修飾的遞歸方法,在方法執(zhí)行時(shí),執(zhí)行線程在獲取鎖之后仍能連續(xù)多次地獲得該鎖。ReentrantLock 雖然不能像 synchronize 關(guān)鍵字一樣支持隱式的重進(jìn)入,但在調(diào)用 lock() 方法時(shí),已經(jīng)獲得鎖的線程,能夠再次調(diào)用 lock() 方法獲取鎖而不被阻塞


            1. 1實(shí)現(xiàn)重進(jìn)入

        重進(jìn)入特性的實(shí)現(xiàn)需要解決以下兩個(gè)問題:

        • 線程再次獲取鎖

          鎖需要去識(shí)別獲取鎖的線程是否為當(dāng)前占據(jù)鎖的線程,如果是,則再次成功獲取

        • 鎖的最終釋放

          線程重復(fù) n 次獲取鎖,隨后在第 n 次釋放該鎖后,其他線程能獲取到鎖。實(shí)現(xiàn)此功能,理應(yīng)考慮使用計(jì)數(shù)

        ReentrantLock 通過組合自定義同步器來實(shí)現(xiàn)鎖的獲取與釋放,以非公平鎖實(shí)現(xiàn)為例,獲取同步狀態(tài)的代碼如下所示,主要是增加了再次獲取同步狀態(tài)的處理邏輯

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 判斷當(dāng)前線程是否為獲取鎖的線程
            else if (current == getExclusiveOwnerThread()) {
                // 將同步值進(jìn)行增加,并返回 true
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        考慮到成功獲取鎖的線程再次獲取鎖,只是增加同步狀態(tài)值,這也就要求 ReentrantLock 在釋放同步狀態(tài)時(shí)減少同步狀態(tài)值,該方法代碼如下:

        protected final boolean tryRelease(int releases) {
            // 減少狀態(tài)值
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 當(dāng)同步狀態(tài)為0,將占有線程設(shè)為null,并返回true,表示釋放成功
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }


           1.2. 公平與非公平獲取鎖的區(qū)別


        如果一個(gè)鎖是公平的,那么鎖的獲取順序就應(yīng)該符合請(qǐng)求的絕對(duì)時(shí)間順序,也即 FIFO。回顧上一節(jié),非公平鎖只要 CAS 設(shè)置同步狀態(tài)成功,即表示當(dāng)前線程獲取了鎖,而公平鎖則不同,代碼如下:

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                /* 
                 * 唯一不同的就是判斷條件多了 hasQueuedPredecessors()
                 * 該方法用來判斷當(dāng)前節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn)
                 * 如果該方法返回 true,表示有線程比當(dāng)前線程更早請(qǐng)求獲取鎖
                 * 因此需要等待前驅(qū)線程釋放鎖之后才能繼續(xù)獲取鎖
                 */
                if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }



        2、讀寫鎖

        之前提到的鎖基本都是排它鎖,同一時(shí)刻只允許一個(gè)線程訪問,而讀寫鎖在同一時(shí)刻可以允許多個(gè)線程訪問,但在寫線程訪問時(shí),所有的讀線程和其他寫線程均被阻塞。讀寫鎖維護(hù)了一對(duì)鎖,一個(gè)讀鎖和一個(gè)寫鎖,通過分離讀鎖和寫鎖,使得并發(fā)性相比一般的排它鎖有了很大提升


        2.1接口示例

        下面通過緩存示例說明讀寫鎖的使用方式

        public class Cache {

            static Map<String, Object> map = new HashMap<>();
            static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
            static Lock r = rwl.readLock();
            static Lock w = rwl.writeLock();

            /**
             * 獲取一個(gè) key 對(duì)應(yīng)的 value
             */
            public static Object get(String key) {
                r.lock();
                try {
                    return map.get(key);
                } finally {
                    r.unlock();
                }
            }

            /**
             * 設(shè)置 key 對(duì)應(yīng)的 value,并返回舊的 value
             */
            public static Object put(String key, Object value) {
                w.lock();
                try {
                    return map.put(key, value);
                } finally {
                    w.unlock();
                }
            }

            /**
             * 清空所有的內(nèi)容
             */
            public static void clear() {
                w.lock();
                try {
                    map.clear();
                } finally {
                    w.unlock();
                }
            }
        }

        2. 2讀寫狀態(tài)的設(shè)計(jì)

        讀寫鎖同樣依賴自定義同步器來實(shí)現(xiàn)功能,而讀寫狀態(tài)就是其同步器狀態(tài)。讀寫鎖的自定義同步器需要在同步狀態(tài)(一個(gè)整型變量)上維護(hù)多個(gè)讀線程和一個(gè)寫線程的狀態(tài),為此需要讀寫鎖將變量切分成兩部分,高 16 位表示讀,低 16 位表示寫

        上圖表示一個(gè)線程已經(jīng)獲取了寫鎖,且重進(jìn)入了兩次,同時(shí)也連續(xù)兩次獲取了讀鎖。通過位運(yùn)算可以迅速確定讀和寫各自的狀態(tài),假設(shè)當(dāng)前同步狀態(tài)值為 S,則:

        • 寫狀態(tài)等于 S & 0x0000FFFF(將高 16 位全部抹去)

        • 讀狀態(tài)等于 S >>> 16(無符號(hào)右移 16 位)

        • 當(dāng)寫狀態(tài)增加 1 時(shí),等于 S + 1

        • 當(dāng)讀狀態(tài)增加 1 時(shí),等于 S + (1<<6),也就是 S + 0x00010000

        根據(jù)狀態(tài)的劃分能得出一個(gè)結(jié)論:S 不等于 0 時(shí),當(dāng)寫狀態(tài)(S & 0x0000FFFF)等于 0 時(shí),則讀狀態(tài)(S >>> 16)大于 0,即讀鎖已被獲取


        2.3寫鎖的獲取與釋放

        寫鎖是一個(gè)支持重進(jìn)入的排它鎖。如果當(dāng)前線程已經(jīng)獲取了寫鎖,則增加寫狀態(tài)。如果當(dāng)前線程在獲取寫鎖時(shí),讀鎖已被獲取,或者該線程不是獲取寫鎖的線程,則當(dāng)前線程進(jìn)入等待狀態(tài),獲取寫鎖的代碼如下:

        protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();
            // exclusiveCount 方法會(huì)用 c & 0x0000FFFF,即得出寫狀態(tài)個(gè)數(shù)
            int w = exclusiveCount(c);
            if (c != 0) {
                // 根據(jù)上面提到的推論,c 不等于 0,而 w 等于 0,證明存在讀鎖
                // 當(dā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;
            }
            if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

        寫鎖的每次釋放均會(huì)減少寫狀態(tài),當(dāng)寫狀態(tài)為 0 時(shí)表示寫鎖已被釋放,從而等待的讀寫線程能夠繼續(xù)訪問讀寫鎖,同時(shí)前次寫線程的修改對(duì)后續(xù)讀寫線程可見


        2.4讀鎖的獲取與釋放

        讀鎖是一個(gè)支持重進(jìn)入的共享鎖,它能被多個(gè)線程同時(shí)獲取,在沒有其他寫線程訪問時(shí),讀鎖總能被成功獲取,這里對(duì)獲取讀鎖的代碼做了簡(jiǎn)化:

        protected final int tryAcquireShared(int unused) {

            for(;;) {
                int c = getState();
                int nextc = c + (1<<16);
                if(nextc < c) {
                    throw new Error("Maximum lock count exceeded");
                }
                // 如果其他線程已經(jīng)獲取寫鎖,則讀取獲取失敗
                if(exclusiveCount(c) != 0 && owner != Thread.currentThread()) {
                    return -1;
                }
                if(compareAndSetState(c, nextc)) {
                    return 1;
                }
            }
        }

        讀鎖的每次釋放均減少讀狀態(tài),減少的值是 1<<16


        2.5鎖降級(jí)

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

        public void processData() {
            readLock.lock();
            if(!update) {
                // 必須先釋放讀鎖
                readLock.unlock();
                // 鎖降級(jí)從寫鎖獲取到開始
                writeLock.lock();
                try {
           if(!update) {
                        // 準(zhǔn)備數(shù)據(jù)的流程(略)
                        update = true;
                    }
                    readLock.lock();
                } finally {
                    writeLock.unlock();
                }
            }
            try {
                // 使用數(shù)據(jù)的流程(略)
            } finally {
                readLock.unlock();
            }
        }

        上例中,當(dāng)數(shù)據(jù)發(fā)生變更,則 update(使用 volatile 修飾)被設(shè)置為 false,此時(shí)所有訪問 processData 方法的線程都能感知到變化,但只有一個(gè)線程能獲取到寫鎖,其余線程會(huì)被阻塞在寫鎖的 lock 方法上。當(dāng)前線程獲取寫鎖完成數(shù)據(jù)準(zhǔn)備之后,再次獲取讀鎖,隨后釋放寫鎖,完成鎖降級(jí)




        鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布

        ??????

        ??長(zhǎng)按上方微信二維碼 2 秒





        感謝點(diǎn)贊支持下哈 

        瀏覽 53
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            久久九九女女男女热 | 国产又爽又黄的视频在线观看 | 日韩一级免费大片 | 成人毛片18女人A片免费观看成人在 | 免费看的黄网站 | 免费看男女黄床上色 | www.草逼网站 | 亚洲小说欧美激情另类 | 一级特黄特色的免费大片视频 | 精品国产乱码久久久久久竹菊影视 |