適用場景
讀操作頻發,寫操作不頻繁。
兩個線程同時讀取同一個共享資源沒有任何問題
如果一個線程對共享資源進行寫操作,此時就不能有其他線程對共享資源進行讀寫
條件分析寫操作的優先級高于讀操作,在讀操作頻繁的場景下,如果寫操作沒有高于讀操作的優先級,就會導致寫操作線程“餓死”的情況發生
讀操作觸發條件:
沒有線程正在執行寫操作
沒有線程在等待執行寫操作
寫操作觸發條件:沒有線程正在執行讀寫操作
代碼實現
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(); ??} } |
ReadWriteLockl類中通過讀鎖、寫鎖以兩個鎖的狀態控制線程的讀、寫操作: writers表示當前正在使用寫鎖的線程數量; writeRequests表示等待請求寫鎖的線程數量; readers表示請求讀鎖的線程數量; 說明: 1.線程在獲取讀鎖的時候,只要沒有線程擁有寫鎖即writers==0同時沒有線程請求寫鎖即writerRquests==0,那么線程就能成功獲取讀鎖; 2.當一個線程想獲取寫鎖的時候,會把寫鎖的請求數加1即writeRequests++,然后再嘗試獲取能否獲取寫鎖,如果當前沒有線程占用寫鎖即writers==0,那么此時就能成功獲取寫鎖,同時writers++;如果wirters>0表示寫鎖此時被其他線程占用那么當前線程會被阻塞等待寫鎖釋放時被喚醒。 3.寫操作的優先級高于讀操作的優先級體現在,線程請求讀鎖時會判斷持有寫鎖的線程數和請求寫鎖的線程數,即while(writers > 0 || writeRequests > 0){wait();},而線程請求寫鎖時只需要判斷持有寫鎖和讀鎖的線程數即可,即while(readers > 0 || writers > 0) {wait();} |
鎖重入,是指同一線程 外層函數獲得鎖之后 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。ReentrantLock 和synchronized 都是可重入鎖,可重入鎖最大的作用是避免死鎖。
以自旋鎖為例,如果自旋鎖不是可重入鎖的話,如果一個線程在第一次獲取鎖執行同步代碼前提下,第二次再執行同步代碼就產生了死鎖。
以前面的代碼為例:
此時有兩個線程Thread1,Thread2
Thread2在Thread1獲取讀鎖以后請求寫鎖,readers=1、writers=0、writeRequests=1
若此時Thread1再次嘗試獲取同一個讀鎖,根據已有的代碼writers > 0 || writeRequests > 0,因為Thread請求寫鎖的原因導致該條件成立,Thread1進入阻塞狀態,死鎖出現
讀鎖重入public class ReadWriteLock{ ?private Map ?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, (getAccessCount(callingThread) + 1)); ?} ?public synchronized void unlockRead(){ ???Thread callingThread = Thread.currentThread(); ???int accessCount = getAccessCount(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.intValue(); } ?private boolean isReader(Thread callingThread){ ???return readingThreads.get(callingThread) != null; ?} } |
讀鎖的可重入有兩種情況: 1.當前程序中沒有線程請求寫鎖(這種情況是幾乎不存在) 2.當前程序中有線程請求寫鎖也有線程請求讀鎖,并且有線程已經得到了讀鎖 第二種情況是最常見的,因此我們需要知道哪些線程是持有讀鎖的 因此在代碼中使用Map來存儲已經持有讀鎖的線程和對應線程獲取讀鎖的次數,通過Map就可以判斷對應的線程是否持有讀鎖,調整之后的代碼在原有判斷"writeRequests >0"和"writers > 0"還加上了判斷當前線程是否持有讀鎖的判斷邏輯 |
public class ReadWriteLock{ ?private Map ?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; ?} } |
寫鎖重入,是在當前程序里有且只有一個線程持有寫鎖,如果寫鎖重入,說明當前程序中沒有線程持有讀鎖,寫鎖重入只有持有寫鎖的線程才能重入,其他的線程就需要進入阻塞狀態 |
public class ReadWriteLock{ private MapreadingThreads = 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.intValue(); } 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 this.writeRequests > 0; } }
參考文獻
http://ifeve.com/read-write-l...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71556.html
摘要:語言在之前,提供的唯一的并發原語就是管程,而且之后提供的并發包,也是以管程技術為基礎的。但是管程更容易使用,所以選擇了管程。線程進入條件變量的等待隊列后,是允許其他線程進入管程的。并發編程里兩大核心問題互斥和同步,都可以由管程來幫你解決。 并發編程這個技術領域已經發展了半個世紀了。有沒有一種核心技術可以很方便地解決我們的并發問題呢?這個問題, 我會選擇 Monitor(管程)技術。Ja...
摘要:此時線程和會再有一個線程能夠獲取寫鎖,假設是,如果不采用再次驗證的方式,此時會再次查詢數據庫。而實際上線程已經把緩存的值設置好了,完全沒有必要再次查詢數據庫。 大家知道了Java中使用管程同步原語,理論上可以解決所有的并發問題。那 Java SDK 并發包里為什么還有很多其他的工具類呢?原因很簡單:分場景優化性能,提升易用性 今天我們就介紹一種非常普遍的并發場景:讀多寫少場景。實際工作...
摘要:公平鎖非公平鎖公平鎖公平鎖是指多個線程按照申請鎖的順序來獲取鎖。加鎖后,任何其他試圖再次加鎖的線程會被阻塞,直到當前進程解鎖。重量級鎖會讓其他申請的線程進入阻塞,性能降低。 Java 中15種鎖的介紹 在讀很多并發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下: 公平鎖 / 非公平鎖 可重入鎖 / 不可重入鎖 獨享鎖 / 共享鎖 互斥鎖 / 讀...
摘要:鎖的使用建議減少鎖持有時間減少鎖粒度讀寫鎖替代獨占鎖鎖分離鎖粗化減少鎖的持有時間減少鎖的持有時間有助于降低沖突的可能性進而提升并發能力減少鎖粒度例如內部分為個加鎖時不會像一樣全局加鎖只需要對相應加鎖但是如果需要獲取全局的信息比如首先會使用無 鎖的使用建議 減少鎖持有時間 減少鎖粒度 讀寫鎖替代獨占鎖 鎖分離 鎖粗化 減少鎖的持有時間 減少鎖的持有時間有助于降低沖突的可能性,進而...
閱讀 1552·2021-09-22 15:52
閱讀 3459·2021-09-22 14:59
閱讀 2843·2021-09-02 15:12
閱讀 971·2021-08-20 09:35
閱讀 1578·2019-08-30 14:09
閱讀 2709·2019-08-30 13:56
閱讀 1646·2019-08-26 18:27
閱讀 3363·2019-08-26 13:37