JAVA的JUC包中的鎖包括"獨佔鎖"和"共享鎖"。JUC中的共享鎖有:CountDownLatch、CyclicBarrier、Semaphore、ReentrantReadWriteLock等。本章會以ReentrantReadWriteLock為藍本對共享鎖進行說明。
一、ReentrantLock與ReentrantReadWriteLock
說到ReentrantReadWriteLock,首先要做的是與ReentrantLock劃清界限。它和後者都是單獨的實現,彼此之間沒有繼承或實現的關係。
ReentrantLock 實現了標準的互斥操作,也就是一次只能有一個執行緒持有鎖,也即所謂獨佔鎖的概念。顯然這個特點在一定程度上面減低了吞吐量,實際上獨佔鎖是一種保守的鎖策略,在這種情況下任何“讀/讀”,“寫/讀”,“寫/寫”操作都不能同時發生。但是同樣需要強調的一個概念是,鎖是有一定的開銷的,當併發比較大的時候,鎖的開銷就比較客觀了。所以如果可能的話就儘量少用鎖,非要用鎖的話就嘗試看能否改造為讀寫鎖。
ReadWriteLock 描述的是:一個資源能夠被多個讀執行緒訪問,或者被一個寫執行緒訪問,但是不能同時存在讀寫執行緒。也就是說讀寫鎖使用的場合是一個共享資源被大量讀取操作,而只有少量的寫操作(修改資料)。
參考:http://my.oschina.net/adan1/blog/158107?fromerr=M4d1zh4s
二、ReadWriteLock 和 ReentrantReadWriteLock
ReadWriteLock,顧名思義,是讀寫鎖。它維護了一對相關的鎖 — — "讀取鎖"和"寫入鎖",一個用於讀取操作,另一個用於寫入操作。
"讀取鎖"用於只讀操作,它是"共享鎖",能同時被多個執行緒獲取。
"寫入鎖"用於寫入操作,它是"獨佔鎖",寫入鎖只能被一個執行緒鎖獲取。 注意:不能同時存在讀取鎖和寫入鎖!
ReadWriteLock是一個介面。ReentrantReadWriteLock是它的實現類,ReentrantReadWriteLock包括子類ReadLock和WriteLock。
ReentrantReadWriteLock的UML類圖如下:
讀寫鎖的機制:"讀-讀"不互斥、"讀-寫"互斥、"寫-寫"互斥。即在任何時候:只有一個執行緒在寫入;執行緒正在讀取的時候,寫入操作等待;執行緒正在寫入的時候,其他執行緒的寫入操作和讀取操作都要等待;
讀鎖的加鎖原始碼片段如下:
寫鎖的加鎖原始碼片段下:
相比較而言,就是上面說過的一旦寫鎖加鎖時發現有其他執行緒進行了操作,則將當前執行緒放置於執行緒等待佇列中——之後再喚醒。而讀操作鎖直接進行了共享執行緒,併發讀取。
鎖降級:從寫鎖變成讀鎖;鎖升級:從讀鎖變成寫鎖。讀鎖是可以被多執行緒共享的,寫鎖是單執行緒獨佔的。也就是說寫鎖的併發限制比讀鎖高,這可能就是升級/降級名稱的來源。
如下程式碼會產生死鎖,因為同一個執行緒中,在沒有釋放讀鎖的情況下,就去申請寫鎖,這屬於鎖升級,ReentrantReadWriteLock是不支援的。
ReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.readLock().lock();
System.out.println("get readLock.");
rtLock.writeLock().lock();
System.out.println("blocking");
ReadWriteLock rtLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock支援鎖降級,如下程式碼不會產生死鎖。
ReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.writeLock().lock();
System.out.println("writeLock");
rtLock.readLock().lock();
System.out.println("get read lock");