深入淺析Java中的ReentrantReadWriteLock共享鎖?相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。
網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、重慶小程序開發(fā)、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了大武口免費建站歡迎大家使用!
ReadWriteLock 和 ReentrantReadWriteLock介紹
ReadWriteLock,顧名思義,是讀寫鎖。它維護了一對相關(guān)的鎖 — — “讀取鎖”和“寫入鎖”,一個用于讀取操作,另一個用于寫入操作。
“讀取鎖”用于只讀操作,它是“共享鎖”,能同時被多個線程獲取。
“寫入鎖”用于寫入操作,它是“獨占鎖”,寫入鎖只能被一個線程鎖獲取。
注意:不能同時存在讀取鎖和寫入鎖!
ReadWriteLock是一個接口。ReentrantReadWriteLock是它的實現(xiàn)類,ReentrantReadWriteLock包括子類ReadLock和WriteLock。
ReadWriteLock 和 ReentrantReadWriteLock函數(shù)列表
ReadWriteLock函數(shù)列表
// 返回用于讀取操作的鎖。 Lock readLock() // 返回用于寫入操作的鎖。 Lock writeLock()
ReentrantReadWriteLock函數(shù)列表
// 創(chuàng)建一個新的 ReentrantReadWriteLock,默認是采用“非公平策略”。 ReentrantReadWriteLock() // 創(chuàng)建一個新的 ReentrantReadWriteLock,fair是“公平策略”。fair為true,意味著公平策略;否則,意味著非公平策略。 ReentrantReadWriteLock(boolean fair) // 返回當前擁有寫入鎖的線程,如果沒有這樣的線程,則返回 null。 protected Thread getOwner() // 返回一個 collection,它包含可能正在等待獲取讀取鎖的線程。 protected Collection<Thread> getQueuedReaderThreads() // 返回一個 collection,它包含可能正在等待獲取讀取或?qū)懭腈i的線程。 protected Collection<Thread> getQueuedThreads() // 返回一個 collection,它包含可能正在等待獲取寫入鎖的線程。 protected Collection<Thread> getQueuedWriterThreads() // 返回等待獲取讀取或?qū)懭腈i的線程估計數(shù)目。 int getQueueLength() // 查詢當前線程在此鎖上保持的重入讀取鎖數(shù)量。 int getReadHoldCount() // 查詢?yōu)榇随i保持的讀取鎖數(shù)量。 int getReadLockCount() // 返回一個 collection,它包含可能正在等待與寫入鎖相關(guān)的給定條件的那些線程。 protected Collection<Thread> getWaitingThreads(Condition condition) // 返回正等待與寫入鎖相關(guān)的給定條件的線程估計數(shù)目。 int getWaitQueueLength(Condition condition) // 查詢當前線程在此鎖上保持的重入寫入鎖數(shù)量。 int getWriteHoldCount() // 查詢是否給定線程正在等待獲取讀取或?qū)懭腈i。 boolean hasQueuedThread(Thread thread) // 查詢是否所有的線程正在等待獲取讀取或?qū)懭腈i。 boolean hasQueuedThreads() // 查詢是否有些線程正在等待與寫入鎖有關(guān)的給定條件。 boolean hasWaiters(Condition condition) // 如果此鎖將公平性設(shè)置為 ture,則返回 true。 boolean isFair() // 查詢是否某個線程保持了寫入鎖。 boolean isWriteLocked() // 查詢當前線程是否保持了寫入鎖。 boolean isWriteLockedByCurrentThread() // 返回用于讀取操作的鎖。 ReentrantReadWriteLock.ReadLock readLock() // 返回用于寫入操作的鎖。 ReentrantReadWriteLock.WriteLock writeLock()
ReentrantReadWriteLock數(shù)據(jù)結(jié)構(gòu)
ReentrantReadWriteLock的UML類圖如下:
從中可以看出:
(01) ReentrantReadWriteLock實現(xiàn)了ReadWriteLock接口。ReadWriteLock是一個讀寫鎖的接口,提供了"獲取讀鎖的readLock()函數(shù)" 和 "獲取寫鎖的writeLock()函數(shù)"。
(02) ReentrantReadWriteLock中包含:sync對象,讀鎖readerLock和寫鎖writerLock。讀鎖ReadLock和寫鎖WriteLock都實現(xiàn)了Lock接口。讀鎖ReadLock和寫鎖WriteLock中也都分別包含了"Sync對象",它們的Sync對象和ReentrantReadWriteLock的Sync對象 是一樣的,就是通過sync,讀鎖和寫鎖實現(xiàn)了對同一個對象的訪問。
(03) 和"ReentrantLock"一樣,sync是Sync類型;而且,Sync也是一個繼承于AQS的抽象類。Sync也包括"公平鎖"FairSync和"非公平鎖"NonfairSync。sync對象是"FairSync"和"NonfairSync"中的一個,默認是"NonfairSync"。
其中,共享鎖源碼相關(guān)的代碼如下:
public static class ReadLock implements Lock, java.io.Serializable { private static final long serialVersionUID = -5992448646407690164L; // ReentrantReadWriteLock的AQS對象 private final Sync sync; protected ReadLock(ReentrantReadWriteLock lock) { sync = lock.sync; } // 獲取“共享鎖” public void lock() { sync.acquireShared(1); } // 如果線程是中斷狀態(tài),則拋出一場,否則嘗試獲取共享鎖。 public void lockInterruptibly() throws InterruptedException { sync.acquireSharedInterruptibly(1); } // 嘗試獲取“共享鎖” public boolean tryLock() { return sync.tryReadLock(); } // 在指定時間內(nèi),嘗試獲取“共享鎖” public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } // 釋放“共享鎖” public void unlock() { sync.releaseShared(1); } // 新建條件 public Condition newCondition() { throw new UnsupportedOperationException(); } public String toString() { int r = sync.getReadLockCount(); return super.toString() + "[Read locks = " + r + "]"; } }
說明:
ReadLock中的sync是一個Sync對象,Sync繼承于AQS類,即Sync就是一個鎖。ReentrantReadWriteLock中也有一個Sync對象,而且ReadLock中的sync和ReentrantReadWriteLock中的sync是對應(yīng)關(guān)系。即ReentrantReadWriteLock和ReadLock共享同一個AQS對象,共享同一把鎖。
ReentrantReadWriteLock中Sync的定義如下:
final Sync sync;
下面,分別從“獲取共享鎖”和“釋放共享鎖”兩個方面對共享鎖進行說明。
獲取共享鎖
獲取共享鎖的思想(即lock函數(shù)的步驟),是先通過tryAcquireShared()嘗試獲取共享鎖。嘗試成功的話,則直接返回;嘗試失敗的話,則通過doAcquireShared()不斷的循環(huán)并嘗試獲取鎖,若有需要,則阻塞等待。doAcquireShared()在循環(huán)中每次嘗試獲取鎖時,都是通過tryAcquireShared()來進行嘗試的。下面看看“獲取共享鎖”的詳細流程。
1. lock()
lock()在ReadLock中,源碼如下:
public void lock() { sync.acquireShared(1); }
2. acquireShared()
Sync繼承于AQS,acquireShared()定義在AQS中。源碼如下:
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
說明:acquireShared()首先會通過tryAcquireShared()來嘗試獲取鎖。
嘗試成功的話,則不再做任何動作(因為已經(jīng)成功獲取到鎖了)。
嘗試失敗的話,則通過doAcquireShared()來獲取鎖。doAcquireShared()會獲取到鎖了才返回。
3. tryAcquireShared()
tryAcquireShared()定義在ReentrantReadWriteLock.java的Sync中,源碼如下:
protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); // 獲取“鎖”的狀態(tài) int c = getState(); // 如果“鎖”是“互斥鎖”,并且獲取鎖的線程不是current線程;則返回-1。 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; // 獲取“讀取鎖”的共享計數(shù) int r = sharedCount(c); // 如果“不需要阻塞等待”,并且“讀取鎖”的共享計數(shù)小于MAX_COUNT; // 則通過CAS函數(shù)更新“鎖的狀態(tài)”,將“讀取鎖”的共享計數(shù)+1。 if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { // 第1次獲取“讀取鎖”。 if (r == 0) { firstReader = current; firstReaderHoldCount = 1; // 如果想要獲取鎖的線程(current)是第1個獲取鎖(firstReader)的線程 } else if (firstReader == current) { firstReaderHoldCount++; } else { // HoldCounter是用來統(tǒng)計該線程獲取“讀取鎖”的次數(shù)。 HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != current.getId()) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); // 將該線程獲取“讀取鎖”的次數(shù)+1。 rh.count++; } return 1; } return fullTryAcquireShared(current); }
說明:tryAcquireShared()的作用是嘗試獲取“共享鎖”。
如果在嘗試獲取鎖時,“不需要阻塞等待”并且“讀取鎖的共享計數(shù)小于MAX_COUNT”,則直接通過CAS函數(shù)更新“讀取鎖的共享計數(shù)”,以及將“當前線程獲取讀取鎖的次數(shù)+1”。否則,通過fullTryAcquireShared()獲取讀取鎖。
4. fullTryAcquireShared()
fullTryAcquireShared()在ReentrantReadWriteLock中定義,源碼如下:
final int fullTryAcquireShared(Thread current) { HoldCounter rh = null; for (;;) { // 獲取“鎖”的狀態(tài) int c = getState(); // 如果“鎖”是“互斥鎖”,并且獲取鎖的線程不是current線程;則返回-1。 if (exclusiveCount(c) != 0) { if (getExclusiveOwnerThread() != current) return -1; // 如果“需要阻塞等待”。 // (01) 當“需要阻塞等待”的線程是第1個獲取鎖的線程的話,則繼續(xù)往下執(zhí)行。 // (02) 當“需要阻塞等待”的線程獲取鎖的次數(shù)=0時,則返回-1。 } else if (readerShouldBlock()) { // 如果想要獲取鎖的線程(current)是第1個獲取鎖(firstReader)的線程 if (firstReader == current) { } else { if (rh == null) { rh = cachedHoldCounter; if (rh == null || rh.tid != current.getId()) { rh = readHolds.get(); if (rh.count == 0) readHolds.remove(); } } // 如果當前線程獲取鎖的計數(shù)=0,則返回-1。 if (rh.count == 0) return -1; } } // 如果“不需要阻塞等待”,則獲取“讀取鎖”的共享統(tǒng)計數(shù); // 如果共享統(tǒng)計數(shù)超過MAX_COUNT,則拋出異常。 if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded"); // 將線程獲取“讀取鎖”的次數(shù)+1。 if (compareAndSetState(c, c + SHARED_UNIT)) { // 如果是第1次獲取“讀取鎖”,則更新firstReader和firstReaderHoldCount。 if (sharedCount(c) == 0) { firstReader = current; firstReaderHoldCount = 1; // 如果想要獲取鎖的線程(current)是第1個獲取鎖(firstReader)的線程, // 則將firstReaderHoldCount+1。 } else if (firstReader == current) { firstReaderHoldCount++; } else { if (rh == null) rh = cachedHoldCounter; if (rh == null || rh.tid != current.getId()) rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); // 更新線程的獲取“讀取鎖”的共享計數(shù) rh.count++; cachedHoldCounter = rh; // cache for release } return 1; } } }
說明:fullTryAcquireShared()會根據(jù)“是否需要阻塞等待”,“讀取鎖的共享計數(shù)是否超過限制”等等進行處理。如果不需要阻塞等待,并且鎖的共享計數(shù)沒有超過限制,則通過CAS嘗試獲取鎖,并返回1。
5. doAcquireShared()
doAcquireShared()定義在AQS函數(shù)中,源碼如下:
private void doAcquireShared(int arg) { // addWaiter(Node.SHARED)的作用是,創(chuàng)建“當前線程”對應(yīng)的節(jié)點,并將該線程添加到CLH隊列中。 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { boolean interrupted = false; for (;;) { // 獲取“node”的前一節(jié)點 final Node p = node.predecessor(); // 如果“當前線程”是CLH隊列的表頭,則嘗試獲取共享鎖。 if (p == head) { int r = tryAcquireShared(arg); if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC if (interrupted) selfInterrupt(); failed = false; return; } } // 如果“當前線程”不是CLH隊列的表頭,則通過shouldParkAfterFailedAcquire()判斷是否需要等待, // 需要的話,則通過parkAndCheckInterrupt()進行阻塞等待。若阻塞等待過程中,線程被中斷過,則設(shè)置interrupted為true。 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
說明:doAcquireShared()的作用是獲取共享鎖。
它會首先創(chuàng)建線程對應(yīng)的CLH隊列的節(jié)點,然后將該節(jié)點添加到CLH隊列中。CLH隊列是管理獲取鎖的等待線程的隊列。
如果“當前線程”是CLH隊列的表頭,則嘗試獲取共享鎖;否則,則需要通過shouldParkAfterFailedAcquire()判斷是否阻塞等待,需要的話,則通過parkAndCheckInterrupt()進行阻塞等待。
doAcquireShared()會通過for循環(huán),不斷的進行上面的操作;目的就是獲取共享鎖。需要注意的是:doAcquireShared()在每一次嘗試獲取鎖時,是通過tryAcquireShared()來執(zhí)行的!
釋放共享鎖
釋放共享鎖的思想,是先通過tryReleaseShared()嘗試釋放共享鎖。嘗試成功的話,則通過doReleaseShared()喚醒“其他等待獲取共享鎖的線程”,并返回true;否則的話,返回flase。
1. unlock()
public void unlock() { sync.releaseShared(1); }
說明:該函數(shù)實際上調(diào)用releaseShared(1)釋放共享鎖。
2. releaseShared()
releaseShared()在AQS中實現(xiàn),源碼如下:
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
說明:releaseShared()的目的是讓當前線程釋放它所持有的共享鎖。
它首先會通過tryReleaseShared()去嘗試釋放共享鎖。嘗試成功,則直接返回;嘗試失敗,則通過doReleaseShared()去釋放共享鎖。
3. tryReleaseShared()
tryReleaseShared()定義在ReentrantReadWriteLock中,源碼如下:
protected final boolean tryReleaseShared(int unused) { // 獲取當前線程,即釋放共享鎖的線程。 Thread current = Thread.currentThread(); // 如果想要釋放鎖的線程(current)是第1個獲取鎖(firstReader)的線程, // 并且“第1個獲取鎖的線程獲取鎖的次數(shù)”=1,則設(shè)置firstReader為null; // 否則,將“第1個獲取鎖的線程的獲取次數(shù)”-1。 if (firstReader == current) { // assert firstReaderHoldCount > 0; if (firstReaderHoldCount == 1) firstReader = null; else firstReaderHoldCount--; // 獲取rh對象,并更新“當前線程獲取鎖的信息”。 } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != current.getId()) rh = readHolds.get(); int count = rh.count; if (count <= 1) { readHolds.remove(); if (count <= 0) throw unmatchedUnlockException(); } --rh.count; } for (;;) { // 獲取鎖的狀態(tài) int c = getState(); // 將鎖的獲取次數(shù)-1。 int nextc = c - SHARED_UNIT; // 通過CAS更新鎖的狀態(tài)。 if (compareAndSetState(c, nextc)) return nextc == 0; } }
說明:tryReleaseShared()的作用是嘗試釋放共享鎖。
4. doReleaseShared()
doReleaseShared()定義在AQS中,源碼如下:
private void doReleaseShared() { for (;;) { // 獲取CLH隊列的頭節(jié)點 Node h = head; // 如果頭節(jié)點不為null,并且頭節(jié)點不等于tail節(jié)點。 if (h != null && h != tail) { // 獲取頭節(jié)點對應(yīng)的線程的狀態(tài) int ws = h.waitStatus; // 如果頭節(jié)點對應(yīng)的線程是SIGNAL狀態(tài),則意味著“頭節(jié)點的下一個節(jié)點所對應(yīng)的線程”需要被unpark喚醒。 if (ws == Node.SIGNAL) { // 設(shè)置“頭節(jié)點對應(yīng)的線程狀態(tài)”為空狀態(tài)。失敗的話,則繼續(xù)循環(huán)。 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // 喚醒“頭節(jié)點的下一個節(jié)點所對應(yīng)的線程”。 unparkSuccessor(h); } // 如果頭節(jié)點對應(yīng)的線程是空狀態(tài),則設(shè)置“文件點對應(yīng)的線程所擁有的共享鎖”為其它線程獲取鎖的空狀態(tài)。 else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } // 如果頭節(jié)點發(fā)生變化,則繼續(xù)循環(huán)。否則,退出循環(huán)。 if (h == head) // loop if head changed break; } }
說明:doReleaseShared()會釋放“共享鎖”。它會從前往后的遍歷CLH隊列,依次“喚醒”然后“執(zhí)行”隊列中每個節(jié)點對應(yīng)的線程;最終的目的是讓這些線程釋放它們所持有的鎖。
公平共享鎖和非公平共享鎖
和互斥鎖ReentrantLock一樣,ReadLock也分為公平鎖和非公平鎖。
公平鎖和非公平鎖的區(qū)別,體現(xiàn)在判斷是否需要阻塞的函數(shù)readerShouldBlock()是不同的。
公平鎖的readerShouldBlock()的源碼如下:
final boolean readerShouldBlock() { return hasQueuedPredecessors(); }
在公平共享鎖中,如果在當前線程的前面有其他線程在等待獲取共享鎖,則返回true;否則,返回false。
非公平鎖的readerShouldBlock()的源碼如下:
final boolean readerShouldBlock() { return apparentlyFirstQueuedIsExclusive(); }
在非公平共享鎖中,它會無視當前線程的前面是否有其他線程在等待獲取共享鎖。只要該非公平共享鎖對應(yīng)的線程不為null,則返回true。
ReentrantReadWriteLock示例
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockTest1 { public static void main(String[] args) { // 創(chuàng)建賬戶 MyCount myCount = new MyCount("4238920615242830", 10000); // 創(chuàng)建用戶,并指定賬戶 User user = new User("Tommy", myCount); // 分別啟動3個“讀取賬戶金錢”的線程 和 3個“設(shè)置賬戶金錢”的線程 for (int i=0; i<3; i++) { user.getCash(); user.setCash((i+1)*1000); } } } class User { private String name; //用戶名 private MyCount myCount; //所要操作的賬戶 private ReadWriteLock myLock; //執(zhí)行操作所需的鎖對象 User(String name, MyCount myCount) { this.name = name; this.myCount = myCount; this.myLock = new ReentrantReadWriteLock(); } public void getCash() { new Thread() { public void run() { myLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() +" getCash start"); myCount.getCash(); Thread.sleep(1); System.out.println(Thread.currentThread().getName() +" getCash end"); } catch (InterruptedException e) { } finally { myLock.readLock().unlock(); } } }.start(); } public void setCash(final int cash) { new Thread() { public void run() { myLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() +" setCash start"); myCount.setCash(cash); Thread.sleep(1); System.out.println(Thread.currentThread().getName() +" setCash end"); } catch (InterruptedException e) { } finally { myLock.writeLock().unlock(); } } }.start(); } } class MyCount { private String id; //賬號 private int cash; //賬戶余額 MyCount(String id, int cash) { this.id = id; this.cash = cash; } public String getId() { return id; } public void setId(String id) { this.id = id; } public int getCash() { System.out.println(Thread.currentThread().getName() +" getCash cash="+ cash); return cash; } public void setCash(int cash) { System.out.println(Thread.currentThread().getName() +" setCash cash="+ cash); this.cash = cash; } }
運行結(jié)果:
Thread-0 getCash start Thread-2 getCash start Thread-0 getCash cash=10000 Thread-2 getCash cash=10000 Thread-0 getCash end Thread-2 getCash end Thread-1 setCash start Thread-1 setCash cash=1000 Thread-1 setCash end Thread-3 setCash start Thread-3 setCash cash=2000 Thread-3 setCash end Thread-4 getCash start Thread-4 getCash cash=2000 Thread-4 getCash end Thread-5 setCash start Thread-5 setCash cash=3000 Thread-5 setCash end
結(jié)果說明:
(01) 觀察Thread0和Thread-2的運行結(jié)果,我們發(fā)現(xiàn),Thread-0啟動并獲取到“讀取鎖”,在它還沒運行完畢的時候,Thread-2也啟動了并且也成功獲取到“讀取鎖”。
因此,“讀取鎖”支持被多個線程同時獲取。
(02) 觀察Thread-1,Thread-3,Thread-5這三個“寫入鎖”的線程。只要“寫入鎖”被某線程獲取,則該線程運行完畢了,才釋放該鎖。
看完上述內(nèi)容,你們掌握深入淺析Java中的ReentrantReadWriteLock共享鎖的方法了嗎?如果還想學到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!
文章題目:深入淺析Java中的ReentrantReadWriteLock共享鎖
網(wǎng)頁路徑:http://bm7419.com/article16/pcgigg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供軟件開發(fā)、做網(wǎng)站、建站公司、網(wǎng)站建設(shè)、企業(yè)網(wǎng)站制作、商城網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)