緩存工具CacheUtil - 併發環境的緩存值存取

緩存工具CacheUtil - 併發環境的緩存值存取

目的

  • 適合併發環境的緩存值存取
  • 讀取緩存值時,只需關注數據來源。不用再關注將源數據存入緩存等後續處理。
  • 應用程序N次讀取數據時,數據源讀取一次,緩存讀取N-1次。

設計

當從緩存查找失敗,則去數據源獲取。獲取成功,存入緩存並返回。java

實現

  • Sourcable
    java /** * 可溯源的。可從數據源獲取數據 */ public interface Sourcable { /** * 從數據源獲取 */ <T> T get(); }spring

  • CacheUtil
    ```java
    public class CacheUtil {
    private static final transient Log logger = LogFactory.getLog(CacheUtil.class);shell

    // @Resource(name = "memCacheServiceImpl")
    private static CacheService cache;數據庫

    /**
       * 獲取value
       * 若是從緩存查找失敗,則嘗試從數據源獲取,獲取成功,存入緩存並返回。
       * @param key
       * @param source Sourcable的實現,用於讀取源數據。
       * @param isShort 是否(短期)暫存
       */
      public static <T> T get(String key, Sourcable source, boolean isShort) {
          logger.debug("-> [" + Thread.currentThread().getId() + "] Enter");
          T value = get(key);
          if (value == null && source != null) {
              // 緩存查找失敗,嘗試從數據源獲取
              logger.debug("-> [" + Thread.currentThread().getId() + "] try to Lock");
              key = key.intern();
              synchronized (key) {
                  logger.debug("-> [" + Thread.currentThread().getId() + "] Locked");
                  value = get(key);
                  // 雙重檢查。防止多線程重複從數據源讀取
                  if (value == null) {
                      // 從數據源獲取
                      value = source.get();
                      if (value != null) {
                          // 存入緩存
                          if (isShort) {
                              // 暫存cache
                              put4short(key, value);
                          } else {
                              // 常規存入cache,正常有效期
                              put(key, value);
                          }
                      }
                      logger.debug("-> [" + Thread.currentThread().getId() + "] Loaded");
                  }
              }
          }
          logger.debug("-> [" + Thread.currentThread().getId() + "] get: " + value);
          return value;
      }
    
      // 其餘代碼省略..

    }
    ```緩存

測試

  • CacheUtilTest
    ```java
    public class CacheUtilTest extends SpringTest {多線程

    @Test
      public void get() throws Exception {
          final Long resourceId = 173L;
          final String cacheKey = Resource.class.getName() + '#' + resourceId;
    
          // 清除
          CacheUtil.delete(cacheKey);
    
          // 1000個線程併發存取
          int num = 1000;
          Executor executor = Executors.newFixedThreadPool(num);
          final CountDownLatch latch = new CountDownLatch(num);
          for (int i = 0; i < num; i++) {
              executor.execute(() -> {
                  // new String(cacheKey)。 用於驗證不一樣key對象時,同步鎖的有效性
                  String key = new String(cacheKey);
                  // 讀取緩存值時,只需關注數據來源。不用再關注將源數據存入緩存。
                  Resource resource = CacheUtil.get(key, new Sourcable() {
                          @Override
                          public <T> T get() {
                              List<Resource> resources = DAOUtil.findByHql("FROM Resource WHERE resourceId=" + resourceId);
                              if (CollectionUtils.isNotEmpty(resources)) {
                                  return resources.get(0);
                              }
                              return null;
                          }
                      });
                  latch.countDown();
              });
          }
          latch.await();
      }

    }
    ```併發

附錄

  • SpringTest
    java @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:context/spring.xml") public abstract class SpringTest {}ide

  • 結果
    10個線程運行結果:
[PAY][DEBUG] 2016-10-14 15:14:20,526 (CacheUtil.java:46).get - [pool-1-thread-2] 14030 
-> [29] Enter
[PAY][DEBUG] 2016-10-14 15:14:20,527 (CacheUtil.java:46).get - [pool-1-thread-8] 14031 
-> [35] Enter
[PAY][DEBUG] 2016-10-14 15:14:20,527 (CacheUtil.java:46).get - [pool-1-thread-10] 14031 
-> [37] Enter
[PAY][DEBUG] 2016-10-14 15:14:20,528 (CacheUtil.java:46).get - [pool-1-thread-9] 14032 
-> [36] Enter
[PAY][DEBUG] 2016-10-14 15:14:20,527 (CacheUtil.java:46).get - [pool-1-thread-4] 14031 
-> [31] Enter
[PAY][DEBUG] 2016-10-14 15:14:20,527 (CacheUtil.java:46).get - [pool-1-thread-7] 14031 
-> [34] Enter
[PAY][DEBUG] 2016-10-14 15:14:20,526 (CacheUtil.java:46).get - [pool-1-thread-6] 14030 
-> [33] Enter
[PAY][DEBUG] 2016-10-14 15:14:20,526 (CacheUtil.java:46).get - [pool-1-thread-5] 14030 
-> [32] Enter
[PAY][DEBUG] 2016-10-14 15:14:20,555 (CacheUtil.java:49).get - [pool-1-thread-7] 14059 
-> [34] try to Lock
[PAY][DEBUG] 2016-10-14 15:14:20,557 (CacheUtil.java:53).get - [pool-1-thread-7] 14061 
-> [34] Locked
[PAY][DEBUG] 2016-10-14 15:14:20,561 (CacheUtil.java:49).get - [pool-1-thread-5] 14065 
-> [32] try to Lock
[PAY][DEBUG] 2016-10-14 15:14:20,555 (CacheUtil.java:49).get - [pool-1-thread-4] 14059 
-> [31] try to Lock
[PAY][DEBUG] 2016-10-14 15:14:20,547 (CacheUtil.java:49).get - [pool-1-thread-10] 14051 
-> [37] try to Lock
[PAY][DEBUG] 2016-10-14 15:14:20,547 (CacheUtil.java:49).get - [pool-1-thread-8] 14051 
-> [35] try to Lock
[PAY][DEBUG] 2016-10-14 15:14:20,547 (CacheUtil.java:49).get - [pool-1-thread-9] 14051 
-> [36] try to Lock
[PAY][DEBUG] 2016-10-14 15:14:20,547 (CacheUtil.java:49).get - [pool-1-thread-2] 14051 
-> [29] try to Lock
[PAY][DEBUG] 2016-10-14 15:14:20,528 (CacheUtil.java:46).get - [pool-1-thread-1] 14032 
-> [28] Enter
[PAY][DEBUG] 2016-10-14 15:14:20,527 (CacheUtil.java:46).get - [pool-1-thread-3] 14031 
-> [30] Enter
[PAY][DEBUG] 2016-10-14 15:14:20,565 (CacheUtil.java:49).get - [pool-1-thread-6] 14069 
-> [33] try to Lock
Hibernate: FROM Resource WHERE resourceId=173
[PAY][DEBUG] 2016-10-14 15:14:20,595 (CacheUtil.java:49).get - [pool-1-thread-1] 14099 
-> [28] try to Lock
[PAY][DEBUG] 2016-10-14 15:14:20,599 (CacheUtil.java:49).get - [pool-1-thread-3] 14103 
-> [30] try to Lock
[PAY][DEBUG] 2016-10-14 15:14:20,639 (CacheUtil.java:72).get - [pool-1-thread-7] 14143 
-> [34] Loaded
[PAY][DEBUG] 2016-10-14 15:14:20,639 (CacheUtil.java:53).get - [pool-1-thread-3] 14143 
-> [30] Locked
[PAY][DEBUG] 2016-10-14 15:14:20,639 (CacheUtil.java:76).get - [pool-1-thread-7] 14143 
-> [34] get: /uploadfiles/resource/2014122313575119.png
[PAY][DEBUG] 2016-10-14 15:14:20,645 (CacheUtil.java:53).get - [pool-1-thread-1] 14149 
-> [28] Locked
[PAY][DEBUG] 2016-10-14 15:14:20,645 (CacheUtil.java:76).get - [pool-1-thread-3] 14149 
-> [30] get: /uploadfiles/resource/2014122313575119.png
[PAY][DEBUG] 2016-10-14 15:14:20,651 (CacheUtil.java:76).get - [pool-1-thread-1] 14155 
-> [28] get: /uploadfiles/resource/2014122313575119.png
[PAY][DEBUG] 2016-10-14 15:14:20,651 (CacheUtil.java:53).get - [pool-1-thread-6] 14155 
-> [33] Locked
[PAY][DEBUG] 2016-10-14 15:14:20,658 (CacheUtil.java:76).get - [pool-1-thread-6] 14162 
-> [33] get: /uploadfiles/resource/2014122313575119.png
[PAY][DEBUG] 2016-10-14 15:14:20,658 (CacheUtil.java:53).get - [pool-1-thread-2] 14162 
-> [29] Locked
[PAY][DEBUG] 2016-10-14 15:14:20,665 (CacheUtil.java:76).get - [pool-1-thread-2] 14169 
-> [29] get: /uploadfiles/resource/2014122313575119.png
[PAY][DEBUG] 2016-10-14 15:14:20,665 (CacheUtil.java:53).get - [pool-1-thread-9] 14169 
-> [36] Locked
[PAY][DEBUG] 2016-10-14 15:14:20,671 (CacheUtil.java:76).get - [pool-1-thread-9] 14175 
-> [36] get: /uploadfiles/resource/2014122313575119.png
[PAY][DEBUG] 2016-10-14 15:14:20,671 (CacheUtil.java:53).get - [pool-1-thread-8] 14175 
-> [35] Locked
[PAY][DEBUG] 2016-10-14 15:14:20,676 (CacheUtil.java:76).get - [pool-1-thread-8] 14180 
-> [35] get: /uploadfiles/resource/2014122313575119.png
[PAY][DEBUG] 2016-10-14 15:14:20,676 (CacheUtil.java:53).get - [pool-1-thread-10] 14180 
-> [37] Locked
[PAY][DEBUG] 2016-10-14 15:14:20,681 (CacheUtil.java:76).get - [pool-1-thread-10] 14185 
-> [37] get: /uploadfiles/resource/2014122313575119.png
[PAY][DEBUG] 2016-10-14 15:14:20,681 (CacheUtil.java:53).get - [pool-1-thread-4] 14185 
-> [31] Locked
[PAY][DEBUG] 2016-10-14 15:14:20,686 (CacheUtil.java:76).get - [pool-1-thread-4] 14190 
-> [31] get: /uploadfiles/resource/2014122313575119.png
[PAY][DEBUG] 2016-10-14 15:14:20,686 (CacheUtil.java:53).get - [pool-1-thread-5] 14190 
-> [32] Locked
[PAY][DEBUG] 2016-10-14 15:14:20,694 (CacheUtil.java:76).get - [pool-1-thread-5] 14198 
-> [32] get: /uploadfiles/resource/2014122313575119.png

只有一個id爲34的線程Loaded,其他線程均等待 線程34 處理(從數據庫查詢結果並存入緩存)完成後,再查找緩存從而命中數據。工具

問題

  • 數據源失效問題 當數據源不存在數據時,一直緩存失敗,則會一直從數據源獲取數據。從而形成數據源負擔。 爲防止這個狀況,能夠再進一步作個過濾保障機制。
相關文章
相關標籤/搜索