鎖降級:從寫鎖變成讀鎖;html
鎖升級:從讀鎖變成寫鎖。java
讀鎖是能夠被多線程共享的,寫鎖是單線程獨佔的。也就是說寫鎖的併發限制比讀鎖高,這可能就是升級/降級名稱的來源。redis
以下代碼會產生死鎖,由於同一個線程中,在沒有釋放讀鎖的狀況下,就去申請寫鎖,這屬於鎖升級,ReentrantReadWriteLock是不支持的。數據庫
ReadWriteLock rtLock = new ReentrantReadWriteLock(); rtLock.readLock().lock(); System.out.println("get readLock."); rtLock.writeLock().lock(); System.out.println("blocking");
ReentrantReadWriteLock支持鎖降級,以下代碼不會產生死鎖。數組
ReadWriteLock rtLock = new ReentrantReadWriteLock(); rtLock.writeLock().lock(); System.out.println("writeLock"); rtLock.readLock().lock(); System.out.println("get read lock");
以上這段代碼雖然不會致使死鎖,但沒有正確的釋放鎖。從寫鎖降級成讀鎖,並不會自動釋放當前線程獲取的寫鎖,仍然須要顯示的釋放,不然別的線程永遠也獲取不到寫鎖。緩存
1 class CachedData { 2 Object data; 3 volatile boolean cacheValid; 4 final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 5 6 public void processCachedData() { 7 rwl.readLock().lock(); 8 if (!cacheValid) { 9 // Must release read lock before acquiring write lock 10 rwl.readLock().unlock(); 11 rwl.writeLock().lock(); 12 try { 13 // Recheck state because another thread might have,acquired write lock and changed state before we did. 14 if (!cacheValid) { 15 data = ... 16 cacheValid = true; 17 } 18 // 在釋放寫鎖以前經過獲取讀鎖降級寫鎖(注意此時尚未釋放寫鎖) 19 rwl.readLock().lock(); 20 } finally { 21 rwl.writeLock().unlock(); // 釋放寫鎖而此時已經持有讀鎖 22 } 23 } 24 25 try { 26 use(data); 27 } finally { 28 rwl.readLock().unlock(); 29 } 30 } 31 }
以上代碼加鎖的順序爲:安全
1. rwl.readLock().lock();多線程
2. rwl.readLock().unlock();併發
3. rwl.writeLock().lock();jvm
4. rwl.readLock().lock();
5. rwl.writeLock().unlock();
6. rwl.readLock().unlock();
以上過程總體講解:
1. 多個線程同時訪問該緩存對象時,都加上當前對象的讀鎖,以後其中某個線程優先查看data數據是否爲空。【加鎖順序序號:1 】
2. 當前查看的線程發現沒有值則釋放讀鎖當即加上寫鎖,準備寫入緩存數據。(不明白爲何釋放讀鎖的話能夠查看上面講解進入寫鎖的前提條件)【加鎖順序序號:2和3 】
3. 爲何還會再次判斷是否爲空值(!cacheValid)是由於第二個、第三個線程得到讀的權利時也是須要判斷是否爲空,不然會重複寫入數據。
4. 寫入數據後先進行讀鎖的降級後再釋放寫鎖。【加鎖順序序號:4和5 】
5. 最後數據數據返回前釋放最終的讀鎖。【加鎖順序序號:6 】
若是不使用鎖降級功能,如先釋放寫鎖,而後得到讀鎖,在這個get過程當中,可能會有其餘線程競爭到寫鎖 或者是更新數據 則得到的數據是其餘線程更新的數據,可能會形成數據的污染,即產生髒讀的問題。
下面,讓咱們來實現真正趨於實際生產環境中的緩存案例:
1 import java.util.HashMap; 2 import java.util.Map; 3 import java.util.concurrent.locks.ReadWriteLock; 4 import java.util.concurrent.locks.ReentrantReadWriteLock; 5 6 public class CacheDemo { 7 /** 8 * 緩存器,這裏假設須要存儲1000左右個緩存對象,按照默認的負載因子0.75,則容量=750,大概估計每個節點鏈表長度爲5個 9 * 那麼數組長度大概爲:150,又有雨設置map大小通常爲2的指數,則最近的數字爲:128 10 */ 11 private Map<String, Object> map = new HashMap<>(128); 12 private ReadWriteLock rwl = new ReentrantReadWriteLock(); 13 public static void main(String[] args) { 14 15 } 16 public Object get(String id){ 17 Object value = null; 18 rwl.readLock().lock();//首先開啓讀鎖,從緩存中去取 19 try{
if(map.get(id) == null){ //若是緩存中沒有釋放讀鎖,上寫鎖 22 rwl.readLock().unlock(); 23 rwl.writeLock().lock(); 24 try{ 25 if(value == null){ //防止多寫線程重複查詢賦值 26 value = "redis-value"; //此時能夠去數據庫中查找,這裏簡單的模擬一下 27 } 28 rwl.readLock().lock(); //加讀鎖降級寫鎖,不明白的能夠查看上面鎖降級的原理與保持讀取數據原子性的講解 29 }finally{ 30 rwl.writeLock().unlock(); //釋放寫鎖 31 } 32 } 33 }finally{ 34 rwl.readLock().unlock(); //最後釋放讀鎖 35 } 36 return value; 37 } 38 }
提示:讀寫鎖以後有一個與它配合使用的有條件的阻塞,能夠實現線程間的通訊,它就是Condition。具體詳情請查看個人博客:併發庫應用之六 & 有條件阻塞Condition應用