成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

讀寫鎖的java實(shí)現(xiàn)

233jl / 677人閱讀

摘要:如何維護(hù)狀態(tài)內(nèi)部維護(hù)的讀寫狀態(tài)是由位碼表示,高位為讀狀態(tài),表示持有讀鎖的線程數(shù),低位為寫狀態(tài),表示寫鎖的重入次數(shù),狀態(tài)的改變通過實(shí)現(xiàn),保證同步。寫鎖降級(jí)到讀鎖有時(shí)擁有寫鎖的線程也希望得到讀鎖。

ReentrantReadWriteLock 如何保證同步

Java中的可重入讀寫鎖ReentrantReadWriteLock是基于AQS(AbstractQueuedSynchronizer)實(shí)現(xiàn)的,查看源碼可以發(fā)現(xiàn)內(nèi)部有一個(gè)Sync對(duì)象繼承自AbstractQueuedSynchronizer,它用來管理同步機(jī)制,java并發(fā)包下的類基本都是用它來提供同步機(jī)制的。

再查看AQS的源碼會(huì)發(fā)現(xiàn)其內(nèi)部全是native方法及包裝這些方法的一些其他方法。這些native方法都是調(diào)用本地方法,利用了運(yùn)行機(jī)器CPU的CAS特性。CAS(CompareAndSet)是一種非阻塞算法來保證同步,它的效率通常要比加鎖算法高很多,因?yàn)樗鼰o阻塞,無掛起和恢復(fù),無死鎖。簡單來說,比較和替換是使用一個(gè)期望值和一個(gè)變量的當(dāng)前值進(jìn)行比較,如果當(dāng)前變量的值與我們期望的值相等,就使用一個(gè)新值替換當(dāng)前變量的值,返回true,否則返回false,線程可以選擇繼續(xù)做其他事情。關(guān)于CAS可以參考其他博文關(guān)于這方面的解釋。

如何維護(hù)狀態(tài)

ReentrantReadWriteLock內(nèi)部維護(hù)的讀寫狀態(tài)是由32位碼表示,高16位為讀狀態(tài),表示持有讀鎖的線程數(shù)(sharedCount),低16位為寫狀態(tài),表示寫鎖的重入次數(shù) (exclusiveCount),狀態(tài)的改變通過AQS實(shí)現(xiàn),保證同步。

關(guān)于ReentrantReadWriteLock的最核心部分大概就是上述兩點(diǎn),這里不再細(xì)致分析具體代碼實(shí)現(xiàn),它注重了效率但實(shí)現(xiàn)方式不容易我們理解一個(gè)讀寫鎖到底該有什么東西。因此這里重點(diǎn)通過一個(gè)wait/notify版本的讀寫鎖如何實(shí)現(xiàn)來深入了解讀寫鎖的原理。

讀寫鎖實(shí)現(xiàn)原理 一個(gè)簡單的讀寫鎖實(shí)現(xiàn)

先讓我們對(duì)讀寫訪問資源的條件做個(gè)概述:

讀取 沒有線程正在做寫操作,且沒有線程在請(qǐng)求寫操作。

寫入 沒有線程正在做讀寫操作。

如果某個(gè)線程想要讀取資源,只要沒有線程正在對(duì)該資源進(jìn)行寫操作且沒有線程請(qǐng)求對(duì)該資源的寫操作即可。我們假設(shè)對(duì)寫操作的請(qǐng)求比對(duì)讀操作的請(qǐng)求更重要,就要提升寫請(qǐng)求的優(yōu)先級(jí)。此外,如果讀操作發(fā)生的比較頻繁,我們又沒有提升寫操作的優(yōu)先級(jí),那么就會(huì)產(chǎn)生“饑餓”現(xiàn)象。請(qǐng)求寫操作的線程會(huì)一直阻塞,直到所有的讀線程都從ReadWriteLock上解鎖了。如果一直保證新線程的讀操作權(quán)限,那么等待寫操作的線程就會(huì)一直阻塞下去,結(jié)果就是發(fā)生“饑餓”。因此,只有當(dāng)沒有線程正在鎖住ReadWriteLock進(jìn)行寫操作,且沒有線程請(qǐng)求該鎖準(zhǔn)備執(zhí)行寫操作時(shí),才能保證讀操作繼續(xù)。

當(dāng)其它線程沒有對(duì)共享資源進(jìn)行讀操作或者寫操作時(shí),某個(gè)線程就有可能獲得該共享資源的寫鎖,進(jìn)而對(duì)共享資源進(jìn)行寫操作。有多少線程請(qǐng)求了寫鎖以及以何種順序請(qǐng)求寫鎖并不重要,除非你想保證寫鎖請(qǐng)求的公平性。

按照上面的敘述,簡單的實(shí)現(xiàn)出一個(gè)讀/寫鎖,代碼如下

public class ReadWriteLock {
    private int readers = 0;
    private int writers = 0;
    private int writeRequests = 0;

    public synchronized void lockRead() throws InterruptedException {
        while (writers > 0 || writeRequests > 0) {
            wait();
        }
        readers++;
    }

    public synchronized void unlockRead() {
        readers--;
        notifyAll();
    }

    public synchronized void lockWrite() throws InterruptedException {
        writeRequests++;

        while (readers > 0 || writers > 0) {
            wait();
        }
        writeRequests--;
        writers++;
    }

    public synchronized void unlockWrite() throws InterruptedException {
        writers--;
        notifyAll();
    }
}

ReadWriteLock類中,讀鎖和寫鎖各有一個(gè)獲取鎖和釋放鎖的方法。

讀鎖的實(shí)現(xiàn)在lockRead()中,只要沒有線程擁有寫鎖(writers==0),且沒有線程在請(qǐng)求寫鎖(writeRequests ==0),所有想獲得讀鎖的線程都能成功獲取。

寫鎖的實(shí)現(xiàn)在lockWrite()中,當(dāng)一個(gè)線程想獲得寫鎖的時(shí)候,首先會(huì)把寫鎖請(qǐng)求數(shù)加1(writeRequests++),然后再去判斷是否能夠真能獲得寫鎖,當(dāng)沒有線程持有讀鎖(readers==0 ),且沒有線程持有寫鎖(writers==0)時(shí)就能獲得寫鎖。有多少線程在請(qǐng)求寫鎖并無關(guān)系。

需要注意的是,在兩個(gè)釋放鎖的方法(unlockRead,unlockWrite)中,都調(diào)用了notifyAll方法,而不是notify。要解釋這個(gè)原因,我們可以想象下面一種情形:

如果有線程在等待獲取讀鎖,同時(shí)又有線程在等待獲取寫鎖。如果這時(shí)其中一個(gè)等待讀鎖的線程被notify方法喚醒,但因?yàn)榇藭r(shí)仍有請(qǐng)求寫鎖的線程存在(writeRequests>0),所以被喚醒的線程會(huì)再次進(jìn)入阻塞狀態(tài)。然而,等待寫鎖的線程一個(gè)也沒被喚醒,就像什么也沒發(fā)生過一樣。如果用的是notifyAll方法,所有的線程都會(huì)被喚醒,然后判斷能否獲得其請(qǐng)求的鎖。

用notifyAll還有一個(gè)好處。如果有多個(gè)讀線程在等待讀鎖且沒有線程在等待寫鎖時(shí),調(diào)用unlockWrite()后,所有等待讀鎖的線程都能立馬成功獲取讀鎖 —— 而不是一次只允許一個(gè)。

讀寫鎖的重入

上面實(shí)現(xiàn)的讀寫鎖(ReadWriteLock) 是不可重入的,當(dāng)一個(gè)已經(jīng)持有寫鎖的線程再次請(qǐng)求寫鎖時(shí),就會(huì)被阻塞。原因是已經(jīng)有一個(gè)寫線程了——就是它自己。此外,考慮下面的例子:

Thread 1 獲得了讀鎖

Thread 2 請(qǐng)求寫鎖,但因?yàn)門hread 1 持有了讀鎖,所以寫鎖請(qǐng)求被阻塞。

Thread 1 再想請(qǐng)求一次讀鎖,但因?yàn)門hread 2處于請(qǐng)求寫鎖的狀態(tài),所以想再次獲取讀鎖也會(huì)被阻塞。

上面這種情形使用前面的ReadWriteLock就會(huì)被鎖定——一種類似于死鎖的情形。不會(huì)再有線程能夠成功獲取讀鎖或?qū)戞i了。

為了讓ReadWriteLock可重入,需要對(duì)它做一些改進(jìn)。下面會(huì)分別處理讀鎖的重入和寫鎖的重入。

讀鎖重入

為了讓ReadWriteLock的讀鎖可重入,我們要先為讀鎖重入建立規(guī)則:

要保證某個(gè)線程中的讀鎖可重入,要么滿足獲取讀鎖的條件(沒有寫或?qū)懻?qǐng)求),要么已經(jīng)持有讀鎖(不管是否有寫請(qǐng)求)。

要確定一個(gè)線程是否已經(jīng)持有讀鎖,可以用一個(gè)map來存儲(chǔ)已經(jīng)持有讀鎖的線程以及對(duì)應(yīng)線程獲取讀鎖的次數(shù),當(dāng)需要判斷某個(gè)線程能否獲得讀鎖時(shí),就利用map中存儲(chǔ)的數(shù)據(jù)進(jìn)行判斷。下面是方法lockRead和unlockRead修改后的的代碼:

public class ReadWriteLock {
    private Map readingThreads = new HashMap();

    private int writers = 0;
    private int writeRequests = 0;

    public synchronized void lockRead() throws InterruptedException {
        Thread callingThread = Thread.currentThread();
        while (!canGrantReadAccess(callingThread)) {
            wait();
        }

        readingThreads.put(callingThread, (getReadAccessCount(callingThread) + 1));
    }

    public synchronized void unlockRead() {
        Thread callingThread = Thread.currentThread();
        int accessCount = getReadAccessCount(callingThread);
        if (accessCount == 1) {
            readingThreads.remove(callingThread);
        } else {
            readingThreads.put(callingThread, (accessCount - 1));
        }
        notifyAll();
    }

    private boolean canGrantReadAccess(Thread callingThread) {
        if (writers > 0)
            return false;
        if (isReader(callingThread))
            return true;
        if (writeRequests > 0)
            return false;
        return true;
    }

    private int getReadAccessCount(Thread callingThread) {
        Integer accessCount = readingThreads.get(callingThread);
        if (accessCount == null)
            return 0;
        return accessCount;
    }

    private boolean isReader(Thread callingThread) {
        return readingThreads.get(callingThread) != null;
    }
}

代碼中我們可以看到,只有在沒有線程擁有寫鎖的情況下才允許讀鎖的重入。此外,重入的讀鎖比寫鎖優(yōu)先級(jí)高。

寫鎖重入

僅當(dāng)一個(gè)線程已經(jīng)持有寫鎖,才允許寫鎖重入(再次獲得寫鎖)。下面是方法lockWrite和unlockWrite修改后的的代碼。

public class ReadWriteLock {
    private Map readingThreads = new HashMap();

    private int writeAccesses = 0;
    private int writeRequests = 0;
    private Thread writingThread = null;

    public synchronized void lockWrite() throws InterruptedException {
        writeRequests++;
        Thread callingThread = Thread.currentThread();
        while (!canGrantWriteAccess(callingThread)) {
            wait();
        }
        writeRequests--;
        writeAccesses++;
        writingThread = callingThread;
    }

    public synchronized void unlockWrite() throws InterruptedException {
        writeAccesses--;
        if (writeAccesses == 0) {
            writingThread = null;
        }
        notifyAll();
    }

    private boolean canGrantWriteAccess(Thread callingThread) {
        if (hasReaders())
            return false;
        if (writingThread == null)
            return true;
        if (!isWriter(callingThread))
            return false;
        return true;
    }

    private boolean hasReaders() {
        return readingThreads.size() > 0;
    }

    private boolean isWriter(Thread callingThread) {
        return writingThread == callingThread;
    }
}

注意在確定當(dāng)前線程是否能夠獲取寫鎖的時(shí)候,是如何處理的。

讀鎖升級(jí)到寫鎖

有時(shí),我們希望一個(gè)擁有讀鎖的線程,也能獲得寫鎖。想要允許這樣的操作,要求這個(gè)線程是唯一一個(gè)擁有讀鎖的線程。writeLock()需要做點(diǎn)改動(dòng)來達(dá)到這個(gè)目的:

public class ReadWriteLock {
    private Map readingThreads = new HashMap();

    private int writeAccesses = 0;
    private int writeRequests = 0;
    private Thread writingThread = null;

    public synchronized void lockWrite() throws InterruptedException {
        writeRequests++;
        Thread callingThread = Thread.currentThread();
        while (!canGrantWriteAccess(callingThread)) {
            wait();
        }
        writeRequests--;
        writeAccesses++;
        writingThread = callingThread;
    }

    public synchronized void unlockWrite() throws InterruptedException {
        writeAccesses--;
        if (writeAccesses == 0) {
            writingThread = null;
        }
        notifyAll();
    }

    private boolean canGrantWriteAccess(Thread callingThread) {
        if (isOnlyReader(callingThread))
            return true;
        if (hasReaders())
            return false;
        if (writingThread == null)
            return true;
        if (!isWriter(callingThread))
            return false;
        return true;
    }

    private boolean hasReaders() {
        return readingThreads.size() > 0;
    }

    private boolean isWriter(Thread callingThread) {
        return writingThread == callingThread;
    }

    private boolean isOnlyReader(Thread callingThread) {
        return readers == 1 && readingThreads.get(callingThread) != null;
    }
}

現(xiàn)在ReadWriteLock類就可以從讀鎖升級(jí)到寫鎖了。

寫鎖降級(jí)到讀鎖

有時(shí)擁有寫鎖的線程也希望得到讀鎖。如果一個(gè)線程擁有了寫鎖,那么自然其它線程是不可能擁有讀鎖或?qū)戞i了。所以對(duì)于一個(gè)擁有寫鎖的線程,再獲得讀鎖,是不會(huì)有什么危險(xiǎn)的。我們僅僅需要對(duì)上面canGrantReadAccess方法進(jìn)行簡單地修改:

public class ReadWriteLock {
    private boolean canGrantReadAccess(Thread callingThread) {
        if (isWriter(callingThread))
            return true;
        if (writingThread != null)
            return false;
        if (isReader(callingThread))
            return true;
        if (writeRequests > 0)
            return false;
        return true;
    }
}
可重入的ReadWriteLock的完整實(shí)現(xiàn)

下面是完整的ReadWriteLock實(shí)現(xiàn)。為了便于代碼的閱讀與理解,簡單對(duì)上面的代碼做了重構(gòu)。重構(gòu)后的代碼如下。

public class ReadWriteLock {

    private Map readingThreads = new HashMap();

    private int writeAccesses = 0;
    private int writeRequests = 0;
    private Thread writingThread = null;

    public synchronized void lockRead() throws InterruptedException {
        Thread callingThread = Thread.currentThread();
        while (!canGrantReadAccess(callingThread)) {
            wait();
        }

        readingThreads.put(callingThread, (getReadAccessCount(callingThread) + 1));
    }

    private boolean canGrantReadAccess(Thread callingThread) {
        if (isWriter(callingThread))
            return true;
        if (hasWriter())
            return false;
        if (isReader(callingThread))
            return true;
        if (hasWriteRequests())
            return false;
        return true;
    }

    public synchronized void unlockRead() {
        Thread callingThread = Thread.currentThread();
        if (!isReader(callingThread)) {
            throw new IllegalMonitorStateException("Calling Thread does not hold a read lock on this ReadWriteLock");
        }
        int accessCount = getReadAccessCount(callingThread);
        if (accessCount == 1) {
            readingThreads.remove(callingThread);
        } else {
            readingThreads.put(callingThread, (accessCount - 1));
        }
        notifyAll();
    }

    public synchronized void lockWrite() throws InterruptedException {
        writeRequests++;
        Thread callingThread = Thread.currentThread();
        while (!canGrantWriteAccess(callingThread)) {
            wait();
        }
        writeRequests--;
        writeAccesses++;
        writingThread = callingThread;
    }

    public synchronized void unlockWrite() throws InterruptedException {
        if (!isWriter(Thread.currentThread())) {
            throw new IllegalMonitorStateException("Calling Thread does not hold the write lock on this ReadWriteLock");
        }
        writeAccesses--;
        if (writeAccesses == 0) {
            writingThread = null;
        }
        notifyAll();
    }

    private boolean canGrantWriteAccess(Thread callingThread) {
        if (isOnlyReader(callingThread))
            return true;
        if (hasReaders())
            return false;
        if (writingThread == null)
            return true;
        if (!isWriter(callingThread))
            return false;
        return true;
    }

    private int getReadAccessCount(Thread callingThread) {
        Integer accessCount = readingThreads.get(callingThread);
        if (accessCount == null)
            return 0;
        return accessCount;
    }

    private boolean hasReaders() {
        return readingThreads.size() > 0;
    }

    private boolean isReader(Thread callingThread) {
        return readingThreads.get(callingThread) != null;
    }

    private boolean isOnlyReader(Thread callingThread) {
        return readingThreads.size() == 1 && readingThreads.get(callingThread) != null;
    }

    private boolean hasWriter() {
        return writingThread != null;
    }

    private boolean isWriter(Thread callingThread) {
        return writingThread == callingThread;
    }

    private boolean hasWriteRequests() {
        return writeRequests > 0;
    }
}
在finally中調(diào)用unlock()

在利用ReadWriteLock來保護(hù)臨界區(qū)時(shí),如果臨界區(qū)可能拋出異常,在finally塊中調(diào)用readUnlock()和writeUnlock()就顯得很重要了。這樣做是為了保證ReadWriteLock能被成功解鎖,然后其它線程可以請(qǐng)求到該鎖。這里有個(gè)例子:

lock.lockWrite();
try{
    //do critical section code, which may throw exception
} finally {
    lock.unlockWrite();
}

上面這樣的代碼結(jié)構(gòu)能夠保證臨界區(qū)中拋出異常時(shí)ReadWriteLock也會(huì)被釋放。如果unlockWrite方法不是在finally塊中調(diào)用的,當(dāng)臨界區(qū)拋出了異常時(shí),ReadWriteLock 會(huì)一直保持在寫鎖定狀態(tài),就會(huì)導(dǎo)致所有調(diào)用lockRead()或lockWrite()的線程一直阻塞。唯一能夠重新解鎖ReadWriteLock的因素可能就是ReadWriteLock是可重入的,當(dāng)拋出異常時(shí),這個(gè)線程后續(xù)還可以成功獲取這把鎖,然后執(zhí)行臨界區(qū)以及再次調(diào)用unlockWrite(),這就會(huì)再次釋放ReadWriteLock。但是如果該線程后續(xù)不再獲取這把鎖了呢?所以,在finally中調(diào)用unlockWrite對(duì)寫出健壯代碼是很重要的。

個(gè)人博客:www.hellolvs.com

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/70664.html

相關(guān)文章

  • Java 中15種鎖的介紹:公平鎖,可重入鎖,獨(dú)享鎖,互斥鎖,樂觀鎖,分段鎖,自旋鎖等等

    摘要:公平鎖非公平鎖公平鎖公平鎖是指多個(gè)線程按照申請(qǐng)鎖的順序來獲取鎖。加鎖后,任何其他試圖再次加鎖的線程會(huì)被阻塞,直到當(dāng)前進(jìn)程解鎖。重量級(jí)鎖會(huì)讓其他申請(qǐng)的線程進(jìn)入阻塞,性能降低。 Java 中15種鎖的介紹 在讀很多并發(fā)文章中,會(huì)提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內(nèi)容如下: 公平鎖 / 非公平鎖 可重入鎖 / 不可重入鎖 獨(dú)享鎖 / 共享鎖 互斥鎖 / 讀...

    LeoHsiun 評(píng)論0 收藏0
  • Java中的鎖以及sychronized實(shí)現(xiàn)機(jī)制

    摘要:有可能,會(huì)造成優(yōu)先級(jí)反轉(zhuǎn)或者饑餓現(xiàn)象。悲觀鎖在中的使用,就是利用各種鎖。對(duì)于而言,其是獨(dú)享鎖。偏向鎖,顧名思義,它會(huì)偏向于第一個(gè)訪問鎖的線程,大多數(shù)情況下鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得。 理解鎖的基礎(chǔ)知識(shí) 如果想要透徹的理解java鎖的來龍去脈,需要先了解以下基礎(chǔ)知識(shí)。 基礎(chǔ)知識(shí)之一:鎖的類型 按照其性質(zhì)分類 公平鎖/非公平鎖 公平鎖是指多個(gè)線程按照申請(qǐng)鎖的順序來獲...

    linkin 評(píng)論0 收藏0
  • Java中的鎖

    摘要:當(dāng)前線程在超時(shí)時(shí)間內(nèi)被中斷超時(shí)時(shí)間結(jié)束,返回釋放鎖獲取等待通知組件,該組件和當(dāng)前的鎖綁定,當(dāng)前線程只有獲取了鎖,才能調(diào)用該組件的方法,調(diào)用后,當(dāng)前線程將釋放鎖。同步器是實(shí)現(xiàn)鎖的關(guān)鍵,在鎖的實(shí)現(xiàn)中聚合同步器,利用同步器實(shí)現(xiàn)鎖的語義。 本文在參考java并發(fā)編程實(shí)戰(zhàn)后完成,參考內(nèi)容較多 Java中的鎖 鎖是用來控制多線程訪問共享資源的方式,一個(gè)鎖能夠防止多個(gè)線程同事訪問共享資源。在Lock...

    gaara 評(píng)論0 收藏0
  • [Java并發(fā)-10] ReadWriteLock:快速實(shí)現(xiàn)一個(gè)完備的緩存

    摘要:此時(shí)線程和會(huì)再有一個(gè)線程能夠獲取寫鎖,假設(shè)是,如果不采用再次驗(yàn)證的方式,此時(shí)會(huì)再次查詢數(shù)據(jù)庫。而實(shí)際上線程已經(jīng)把緩存的值設(shè)置好了,完全沒有必要再次查詢數(shù)據(jù)庫。 大家知道了Java中使用管程同步原語,理論上可以解決所有的并發(fā)問題。那 Java SDK 并發(fā)包里為什么還有很多其他的工具類呢?原因很簡單:分場(chǎng)景優(yōu)化性能,提升易用性 今天我們就介紹一種非常普遍的并發(fā)場(chǎng)景:讀多寫少場(chǎng)景。實(shí)際工作...

    nevermind 評(píng)論0 收藏0
  • Java多線程進(jìn)階(十)—— J.U.C之locks框架:基于AQS的讀寫鎖(5)

    摘要:關(guān)于,最后有兩點(diǎn)規(guī)律需要注意當(dāng)?shù)牡却?duì)列隊(duì)首結(jié)點(diǎn)是共享結(jié)點(diǎn),說明當(dāng)前寫鎖被占用,當(dāng)寫鎖釋放時(shí),會(huì)以傳播的方式喚醒頭結(jié)點(diǎn)之后緊鄰的各個(gè)共享結(jié)點(diǎn)。當(dāng)?shù)牡却?duì)列隊(duì)首結(jié)點(diǎn)是獨(dú)占結(jié)點(diǎn),說明當(dāng)前讀鎖被使用,當(dāng)讀鎖釋放歸零后,會(huì)喚醒隊(duì)首的獨(dú)占結(jié)點(diǎn)。 showImg(https://segmentfault.com/img/remote/1460000016012293); 本文首發(fā)于一世流云的專欄:...

    dunizb 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<