當從緩存查找失敗,則去數據源獲取。獲取成功,存入緩存並返回。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
[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 處理(從數據庫查詢結果並存入緩存)完成後,再查找緩存從而命中數據。工具