缓存工具CacheUtil - 并发环境的缓存值存取

目的

  • 适合并发环境的缓存值存取
  • 读取缓存值时,只需关注数据来源。不用再关注将源数据存入缓存等后续处理。
  • 应用程序N次读取数据时,数据源读取一次,缓存读取N-1次。

设计

当从缓存查找失败,则去数据源获取。获取成功,存入缓存并返回。

实现

  • Sourcable

    /**
    * 可溯源的。可从数据源获取数据
    */
    public interface Sourcable {
    /**
    * 从数据源获取
    */
    <T> T get();
    }
  • CacheUtil

    public class CacheUtil {
    private static final transient Log logger = LogFactory.getLog(CacheUtil.class); // @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
    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

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

    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 处理(从数据库查询结果并存入缓存)完成后,再查找缓存从而命中数据。

问题

  • 数据源失效问题

    当数据源不存在数据时,一直缓存失败,则会一直从数据源获取数据。从而造成数据源负担。

    为防止这个情况,可以再进一步做个过滤保障机制。

缓存工具CacheUtil - 并发环境的缓存值存取的更多相关文章

  1. Cache【硬盘缓存工具类(包含内存缓存LruCache和磁盘缓存DiskLruCache)】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 内存缓存LruCache和磁盘缓存DiskLruCache的封装类,主要用于图片缓存. 效果图 代码分析 内存缓存LruCache和 ...

  2. 如何在高并发环境下设计出无锁的数据库操作(Java版本)

    一个在线2k的游戏,每秒钟并发都吓死人.传统的hibernate直接插库基本上是不可行的.我就一步步推导出一个无锁的数据库操作. 1. 并发中如何无锁. 一个很简单的思路,把并发转化成为单线程.Jav ...

  3. 【高并发】高并发环境下构建缓存服务需要注意哪些问题?我和阿里P9聊了很久!

    写在前面 周末,跟阿里的一个朋友(去年晋升为P9了)聊了很久,聊的内容几乎全是技术,当然了,两个技术男聊得最多的话题当然就是技术了.从基础到架构,从算法到AI,无所不谈.中间又穿插着不少天马行空的想象 ...

  4. Java 使用Redis缓存工具的图文详细方法

    开始在 Java 中使用 Redis 前, 我们需要确保已经安装了 redis 服务及 Java redis 驱动,且你的机器上能正常使用 Java. (1)Java的安装配置可以参考我们的 Java ...

  5. Go/Python/Erlang编程语言对比分析及示例 基于RabbitMQ.Client组件实现RabbitMQ可复用的 ConnectionPool(连接池) 封装一个基于NLog+NLog.Mongo的日志记录工具类LogUtil 分享基于MemoryCache(内存缓存)的缓存工具类,C# B/S 、C/S项目均可以使用!

    Go/Python/Erlang编程语言对比分析及示例   本文主要是介绍Go,从语言对比分析的角度切入.之所以选择与Python.Erlang对比,是因为做为高级语言,它们语言特性上有较大的相似性, ...

  6. 从缓存入门到并发编程三要素详解 Java中 volatile 、final 等关键字解析案例

    引入高速缓存概念 在计算机在执行程序时,以指令为单位来执行,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入. 由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这 ...

  7. 三分钟学会缓存工具DiskLruCache

    DiskLruCache是一个十分好用的android缓存工具,我们可以从GitHub上下载其源码:https://github.com/JakeWharton/DiskLruCache DiskLr ...

  8. Java高并发--CPU多级缓存与Java内存模型

    Java高并发--CPU多级缓存与Java内存模型 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 CPU多级缓存 为什么需要CPU缓存:CPU的频率太快,以至于主存跟 ...

  9. No caching ——无缓存工具

    No caching ——无缓存工具 无缓存工具阻止客户端应用程序(如Web浏览器)缓存任何资源,因此,请求总是发送到远程站点,所以我们总能看到最新版本. 适用场景 开发每次新部署了一版环境,说解决了 ...

随机推荐

  1. meta 标签 详细说明

    meta 标签可提供页面元素信息, 使用键值对的定义方式,可以记录网页上的主要信息,也可以自定义键值对 属性 content(必须),name,http-equiv,scheme,lang 常用 me ...

  2. (error) MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about t

    运行redis过程中,突然报错如下: (error) MISCONF Redis is configured to save RDB snapshots, but is currently not a ...

  3. bootstrapvalidator校验、校验清除重置

    //保存 function saveAdmin(){ //开启验证 $('#saveadmin_form').data('bootstrapValidator').validate(); if(!$( ...

  4. [MongDB] 主从架构--官方极力不推荐

    一.缘由: 看着数据库大家庭都有主从模式,想着Mongodb应该也不会落下.但从官网看来,先是早先舍弃了Master-Master模式,现在又在不推荐 Master-Slave模式,这是要标新立异呀. ...

  5. JAVA下载文件中文乱码问题

    http://blog.itpub.net/92037/viewspace-788900/ 最后的中文乱码没有解决 现在我在系统中用到了两个组件,smartupload,一个支持中文,一个不支持.但是 ...

  6. Window 端口查询

    1. Window环境下查询端口使用情况 方法1: 使用netstat [-参数]|findstr [端口号]来直接查询某个端口的具体使用情况 示例: netstat -ano|findstr &qu ...

  7. winform窗体之间通过 windows API SendMessage函数传值

    -----------------------------------------------------------‘接收窗体’代码.cs------------------------------ ...

  8. (转)ArcGIS制图技巧

    ArcGIS制图技巧(转载自新浪博客)   1  引言 1.1  制图的目的 随着GIS在各行各业的深入应用,各信息化部门和生产单位都逐渐建立起自己的GIS的应用,同时积累了大量的地理数据.随着应用深 ...

  9. Github上传自己的工程

    1.注册并新建项目 2.配置github for windows 前题:安装相应的github for windows 2.1 获取密钥 可以用命令的模式(Git bash),参考资料中有相应的用法: ...

  10. hadoop 2.7.2 和 spark1.6 多节点安装

    一共三个节点,在安装完hadoop之后直接安装spark.下载的spark版本是不带hadoop的,注意节点配置 Hadoop multi-nodes Installation Environment ...