通過ReentrantReadWriteLock做一個緩存工具

JDK1.5開始就出現了java.util.concurrent.locks.ReentrantReadWriteLock,將鎖細分程了讀鎖和寫鎖,

  • 讀鎖:屬於共享鎖,多個線程可同時獲取該鎖,在讀鎖代碼中獲取不到寫鎖,只有讀鎖釋放後才能獲取到寫鎖
  • 寫鎖:屬於獨享鎖,只有一個線程能獲取到該鎖並操作數據,在寫鎖中還可以獲取到讀鎖(寫鎖的降級)

可以同通過這兩種鎖做一些緩存工具-代碼如下:

package com.milla.study.netbase.expert.concurrent.lock;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @Package: com.milla.study.netbase.expert.concurrent.lock
 * @Description: <>
 * @Author: MILLA
 * @CreateDate: 2020/6/1 14:19
 * @UpdateUser: MILLA
 * @UpdateDate: 2020/6/1 14:19
 * @UpdateRemark: <>
 * @Version: 1.0
 */
public class CacheDataTests {
    //讀寫鎖
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    //讀鎖
    ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    //寫鎖
    ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    //緩存數據
    Map<String, Object> data = new HashMap<>();
    static final ThreadPoolExecutor executor = new
            ThreadPoolExecutor(8, 12, 10,
            TimeUnit.SECONDS, new ArrayBlockingQueue<>(30), new ThreadPoolExecutor.CallerRunsPolicy());//如果線程池沒有關閉直接運行任務(性能不高)

    public static void main(String[] args) {
        CacheDataTests cacheData = new CacheDataTests();
        for (int i = 0; i < 1000; i++) {
            Double random = Math.random() * 10;
            int key = random.intValue();
            executor.submit(() -> System.out.println(cacheData.cache(key + "")));
        }
        executor.shutdown();
    }
    //-從緩存中獲取數據,如果不存在及從數據庫或者其他地方獲取數據,當前爲模擬自動生成數據
    public Object cache(String key) {
        try {
            //加讀鎖-讀鎖屬於共享鎖可以支持多個線程獲取鎖,但此時排斥寫鎖
            readLock.lock();
            //如果有對應的key
            if (Objects.isNull(data.get(key))) {
                //解除讀鎖
                readLock.unlock();
                //添加寫鎖,保證寫鎖內操作只有一個線程能執行-避免該處過多線程操作導致崩潰或數據不一致
                writeLock.lock();
                try {
                    //雙重判斷,防止有其他線程已經處理,出現重複處理的情況
                    if (Objects.isNull(data.get(key))) {
                        //獲取數據 TODO 需要進行的具體操作
                        String s = UUID.randomUUID().toString();
                        System.out.println("key: " + key + " value: " + s);
                        data.put(key, s);
                    }
                    //在沒釋放寫鎖前,加讀鎖,將其他線程的可能進行的寫鎖屏蔽-降級寫鎖處理,保證數據的一致性
                    readLock.lock();
                } finally {
                    //最終釋放寫鎖-此時仍然存在讀鎖
                    writeLock.unlock();
                }
            }
            return data.get(key);
        } finally {
            //最終釋放讀鎖
            readLock.unlock();
        }
    }
}

 

PS:不得不感嘆大神們確實是奇思妙想,很多年前就給出了自己的獨到見解。 工具類直接參照JDK,僞代碼如下:

 

class CachedData {
    Object data;
    volatile boolean cacheValid;
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
 
    void processCachedData() {
      rwl.readLock().lock();
      if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        try {
          // Recheck state because another thread might have
          // acquired write lock and changed state before we did.
          if (!cacheValid) {
            data = ...
            cacheValid = true;
          }
          // Downgrade by acquiring read lock before releasing write lock
          rwl.readLock().lock();
        } finally {
          rwl.writeLock().unlock(); // Unlock write, still hold read
        }
      }
 
      try {
        use(data);
      } finally {
        rwl.readLock().unlock();
      }
    }
  }
}

 

//也可以定義一個字典類
class RWDictionary {
    private final Map<String, Data> m = new TreeMap<String, Data>();
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();
 
    public Data get(String key) {
      r.lock();
      try { return m.get(key); }
      finally { r.unlock(); }
    }
    public String[] allKeys() {
      r.lock();
      try { return m.keySet().toArray(); }
      finally { r.unlock(); }
    }
    public Data put(String key, Data value) {
      w.lock();
      try { return m.put(key, value); }
      finally { w.unlock(); }
    }
    public void clear() {
      w.lock();
      try { m.clear(); }
      finally { w.unlock(); }
    }
  }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章