日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java 对象 读写锁_读写锁的java实现

發布時間:2025/3/20 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 对象 读写锁_读写锁的java实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

ReentrantReadWriteLock

如何保證同步

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

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

如何維護狀態

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

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

讀寫鎖實現原理

一個簡單的讀寫鎖實現

先讓我們對讀寫訪問資源的條件做個概述:

讀取 沒有線程正在做寫操作,且沒有線程在請求寫操作。

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

如果某個線程想要讀取資源,只要沒有線程正在對該資源進行寫操作且沒有線程請求對該資源的寫操作即可。我們假設對寫操作的請求比對讀操作的請求更重要,就要提升寫請求的優先級。此外,如果讀操作發生的比較頻繁,我們又沒有提升寫操作的優先級,那么就會產生“饑餓”現象。請求寫操作的線程會一直阻塞,直到所有的讀線程都從ReadWriteLock上解鎖了。如果一直保證新線程的讀操作權限,那么等待寫操作的線程就會一直阻塞下去,結果就是發生“饑餓”。因此,只有當沒有線程正在鎖住ReadWriteLock進行寫操作,且沒有線程請求該鎖準備執行寫操作時,才能保證讀操作繼續。

當其它線程沒有對共享資源進行讀操作或者寫操作時,某個線程就有可能獲得該共享資源的寫鎖,進而對共享資源進行寫操作。有多少線程請求了寫鎖以及以何種順序請求寫鎖并不重要,除非你想保證寫鎖請求的公平性。

按照上面的敘述,簡單的實現出一個讀/寫鎖,代碼如下

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類中,讀鎖和寫鎖各有一個獲取鎖和釋放鎖的方法。

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

寫鎖的實現在lockWrite()中,當一個線程想獲得寫鎖的時候,首先會把寫鎖請求數加1(writeRequests++),然后再去判斷是否能夠真能獲得寫鎖,當沒有線程持有讀鎖(readers==0 ),且沒有線程持有寫鎖(writers==0)時就能獲得寫鎖。有多少線程在請求寫鎖并無關系。

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

如果有線程在等待獲取讀鎖,同時又有線程在等待獲取寫鎖。如果這時其中一個等待讀鎖的線程被notify方法喚醒,但因為此時仍有請求寫鎖的線程存在(writeRequests>0),所以被喚醒的線程會再次進入阻塞狀態。然而,等待寫鎖的線程一個也沒被喚醒,就像什么也沒發生過一樣。如果用的是notifyAll方法,所有的線程都會被喚醒,然后判斷能否獲得其請求的鎖。

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

讀寫鎖的重入

上面實現的讀寫鎖(ReadWriteLock) 是不可重入的,當一個已經持有寫鎖的線程再次請求寫鎖時,就會被阻塞。原因是已經有一個寫線程了——就是它自己。此外,考慮下面的例子:

Thread 1 獲得了讀鎖

Thread 2 請求寫鎖,但因為Thread 1 持有了讀鎖,所以寫鎖請求被阻塞。

Thread 1 再想請求一次讀鎖,但因為Thread 2處于請求寫鎖的狀態,所以想再次獲取讀鎖也會被阻塞。

上面這種情形使用前面的ReadWriteLock就會被鎖定——一種類似于死鎖的情形。不會再有線程能夠成功獲取讀鎖或寫鎖了。

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

讀鎖重入

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

要保證某個線程中的讀鎖可重入,要么滿足獲取讀鎖的條件(沒有寫或寫請求),要么已經持有讀鎖(不管是否有寫請求)。

要確定一個線程是否已經持有讀鎖,可以用一個map來存儲已經持有讀鎖的線程以及對應線程獲取讀鎖的次數,當需要判斷某個線程能否獲得讀鎖時,就利用map中存儲的數據進行判斷。下面是方法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;

}

}

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

寫鎖重入

僅當一個線程已經持有寫鎖,才允許寫鎖重入(再次獲得寫鎖)。下面是方法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;

}

}

注意在確定當前線程是否能夠獲取寫鎖的時候,是如何處理的。

讀鎖升級到寫鎖

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

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;

}

}

現在ReadWriteLock類就可以從讀鎖升級到寫鎖了。

寫鎖降級到讀鎖

有時擁有寫鎖的線程也希望得到讀鎖。如果一個線程擁有了寫鎖,那么自然其它線程是不可能擁有讀鎖或寫鎖了。所以對于一個擁有寫鎖的線程,再獲得讀鎖,是不會有什么危險的。我們僅僅需要對上面canGrantReadAccess方法進行簡單地修改:

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的完整實現

下面是完整的ReadWriteLock實現。為了便于代碼的閱讀與理解,簡單對上面的代碼做了重構。重構后的代碼如下。

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中調用unlock()

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

lock.lockWrite();

try{

//do critical section code, which may throw exception

} finally {

lock.unlockWrite();

}

上面這樣的代碼結構能夠保證臨界區中拋出異常時ReadWriteLock也會被釋放。如果unlockWrite方法不是在finally塊中調用的,當臨界區拋出了異常時,ReadWriteLock 會一直保持在寫鎖定狀態,就會導致所有調用lockRead()或lockWrite()的線程一直阻塞。唯一能夠重新解鎖ReadWriteLock的因素可能就是ReadWriteLock是可重入的,當拋出異常時,這個線程后續還可以成功獲取這把鎖,然后執行臨界區以及再次調用unlockWrite(),這就會再次釋放ReadWriteLock。但是如果該線程后續不再獲取這把鎖了呢?所以,在finally中調用unlockWrite對寫出健壯代碼是很重要的。

總結

以上是生活随笔為你收集整理的java 对象 读写锁_读写锁的java实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。