十分钟掌握Java本地缓存

这篇具有很好参考价值的文章主要介绍了十分钟掌握Java本地缓存。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

—————————— Yesterday is history, tomorrow is a mystery, but today is a gift. That is why it’s called the present. ——————————


缓存是Java开发中经常用到的组件,我们会使用缓存来存储一些不经常改变热点数据,提高系统处理效率,其根本原因在于内存和硬盘读写速度的巨大差异。
Java 1.8中有多个本地缓存,主要是Guava和Caffine,当然我们也可以自己设置一个HashMap来当做缓存使用,接下来一一介绍这几种本地缓存。


1、使用HashMap当做缓存

这是个比较简单的方法,最好是使用ConcurrtHashMap,ConcurrtHashMap兼顾了效率和并发一致性。这里简单用ConcurrtHashMap实现了一个本地缓存,当我们根据code查询对应城市时,如果cache中没有或者缓存已过期,则会从模拟的数据库中查询,这里设置的超时时间为3秒。

public class ConcurrentHashMapTest {
    private static ConcurrentHashMap<Integer, Tuple2<Long, String>> cache = new ConcurrentHashMap<>(3);
    private static Map<Integer, String> dbData = new HashMap<>(3);
    static {
        dbData.put(1, "shanghai");
        dbData.put(2, "beijing");
        dbData.put(3, "shenzhen");
    }
    private static int expirationTime = 3;
    private static int mill = 1000;

    @Test
    @SneakyThrows
    public void test() {
        System.out.println("the result is " + getCityByCode(1));
        Thread.sleep(1000);
        System.out.println("the result is " + getCityByCode(1));
        Thread.sleep(3000);
        System.out.println("the result is " + getCityByCode(1));
        Thread.sleep(1000);
        System.out.println("the result is " + getCityByCode(2));
    }

    private String getCityByCode(int code) {
        if (!cache.containsKey(code)) {
            return getCityFromDb(code);
        }
        Tuple2<Long, String> tuple2 = cache.get(code);
        if (isOverTime(tuple2._1)) {
            System.out.println("cache is over time");
            return getCityFromDb(code);
        } else {
            return tuple2._2;
        }
    }

    private String getCityFromDb(Integer code) {
        String city = dbData.get(code);
        System.out.println("query city " + city + " from db");
        cache.put(code, new Tuple2<>(System.currentTimeMillis(), city));
        return city;
    }

    private boolean isOverTime(Long time) {
        if ((System.currentTimeMillis() - time) / mill > expirationTime) {
            return true;
        }
        return false;
    }
}

输出结果:
query city shanghai from db
the result is shanghai
the result is shanghai
cache is over time
query city shanghai from db
the result is shanghai
query city beijing from db
the result is beijing
2、Guava Cache的使用

Guava是Google提供的一套Java工具包,而Guava Cache是一套非常完善的本地缓存机制(JVM缓存)。Guava cache的设计来源于CurrentHashMap,可以按照多种策略来清理存储在其中的缓存值且保持很高的并发读写性能。接下来介绍一下Guava Cache的常用方法,首先还是创建一些公共方法用于测试,这里用静态方法模拟从数据库查询数据。

private static final int THREE = 3;
private static Map<Integer, String> dbData = new HashMap<>(3);
static {
    dbData.put(1, "shanghai");
    dbData.put(2, "beijing");
    dbData.put(3, "shenzhen");
}

@SneakyThrows
private static void sleep(long millis) {
    Thread.sleep(millis);
}

private static String getCityFromDb(Integer code) {
    sleep(1000);
    return dbData.get(code) + " " + System.currentTimeMillis();
}

private static ListeningExecutorService poolExecutor1 = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(20));

主要包括两种构建缓存的方式,包括LoadingCache和CallableCache 。LoadingCache是指在构建缓存时就设置好加载方法,CallableCache则是在每次获取缓存时设置加载方法。
这里将 Guava Cache 中的方法分为4类进行介绍,如下所示:

  1. 构建缓存的方法,包括LoadingCache和CallableCache;
  2. 缓存刷新策略(refreshAfterWrite)和缓存过期策略(expireAfterWrite和expireAfterAccess)
  3. 缓存过期方法,包括主动使缓存过期(invalidate和invalidateAll)和基于引用的回收策略(weakKeys、weakValues和softValues)
  4. 其他方法,例如recordStats(记录缓存命中状态)、maximumSize(设定缓存的最大个数)
2.1 构建 Guava Cache 的方法

可以分为两种,一种是创建缓存时就设置好,一种是每次获取缓存时设置。前者的好处是不用每次都设置,后者的好处是每次可以设置不同的数据源,按需设置即可。

private static void createDemo() {
     LoadingCache<Integer, String> guavaLoadingCache = CacheBuilder
            .newBuilder()
            .build(new CacheLoader<Integer, String>() {
                @Override
                public String load(Integer code) {
                    return getCityFromDb(code);
                }
            });

     Cache<Integer, String> guavaCallableCache = CacheBuilder
            .newBuilder()
            .build();
    System.out.println(guavaLoadingCache.get(1));
    System.out.println(guavaCallableCache.get(1, () -> getCityFromDb(1)));
}

输出结果:
shanghai 1667722957611
shanghai 1667722958651
2.2 缓存刷新和过期策略

缓存过期有两种策略,一种是在之后一段时候过期(expireAfterWrite),另一种是在读或者写之后一段时间过期(expireAfterAccess),二者的区别在于一个是写,一个是读或者写。缓存刷新则是只有在之后一段时候刷新(refreshAfterWrite)的策略,这里也介绍下removalListener方法,这个方法可以在缓存过期时执行一些操作,缓存刷新和过期的示例如下:

@SneakyThrows
private static void expireAndRefreshDemo() {
    LoadingCache<Integer, String> expireCache = CacheBuilder.newBuilder()
            .expireAfterAccess(1000, TimeUnit.MILLISECONDS)
            .removalListener(removalNotification ->
                    System.out.println("the key " + removalNotification.getKey() + " is removed"))
            .build(new CacheLoader<Integer, String>() {
                @Override
                public String load(Integer integer) {
                    return getCityFromDb(integer);
                }
            });
    expireCache.put(1, "init value");
    System.out.println(expireCache.get(1));
    Thread.sleep(1500);
    System.out.println(expireCache.get(1));
    System.out.println("————————————————");

    LoadingCache<Integer, String> refreshCache = CacheBuilder.newBuilder()
            .refreshAfterWrite(1000, TimeUnit.MILLISECONDS)
            .removalListener(removalNotification ->
                    System.out.println("the key " + removalNotification.getKey() + " is removed"))
            .build(new CacheLoader<Integer, String>() {
                @Override
                public String load(Integer integer) {
                    return getCityFromDb(integer);
                }
            });
    System.out.println(refreshCache.get(1));
    Thread.sleep(1500);
    System.out.println(refreshCache.get(1));
}

输出结果:
init value
the key 1 is removed
shanghai 1667722979092
————————————————
shanghai 1667722980096
the key 1 is removed
shanghai 1667722982599

上述示例中分别新建了两个缓存,缓存刷新和失效的时间均为1000ms,可以看到1500ms后缓存都会失效。注意,如果多个缓存过期策略同时使用,则会同时生效,示例如下:

@SneakyThrows
private static void expireDemo() {
    LoadingCache<Integer, String> cache = CacheBuilder.newBuilder()
            .expireAfterWrite(1800, TimeUnit.MILLISECONDS)
            .expireAfterAccess(1300, TimeUnit.MILLISECONDS)
            .removalListener(removalNotification ->
                    System.out.println("the key " + removalNotification.getKey() + " is removed"))
            .build(new CacheLoader<Integer, String>() {
                @Override
                public String load(Integer integer) {
                    return getCityFromDb(integer);
                }
            });
    cache.put(1, "init value");
    System.out.println(cache.get(1));
    Thread.sleep(1000);
    System.out.println(cache.get(1));
    Thread.sleep(1000);
    System.out.println(cache.get(1));
    Thread.sleep(1500);
    System.out.println(cache.get(1));
}

输出结果:
init value
init value
the key 1 is removed
shanghai 1667723200491
the key 1 is removed
shanghai 1667723202999

可以看到,这里设置了两个缓存过期策略,分别是写后1800ms过期和获取后1300ms过期。然后每隔1000ms读取一次,可以看到2000ms时触发了写后1800ms过期策略。之后等待1500ms后再次读取,触发了获取后1300ms过期策略。后面单独介绍缓存失效和缓存刷新的区别,以及在高并发的场景下查询缓存如何避免缓存穿透。

2.2 缓存过期方法

主动使缓存过期的方法包括invalidate和invalidateAll,invalidate是使单个缓存过期,invalidateAll是使全部缓存过期,示例如下。

@SneakyThrows
private static void invalidateDemo() {
    LoadingCache<Integer, String> cache = CacheBuilder.newBuilder()
            .removalListener(removalNotification ->
                    System.out.println("the key " + removalNotification.getKey() + " is removed"))
            .build(new CacheLoader<Integer, String>() {
                @Override
                public String load(Integer integer) {
                    return getCityFromDb(integer);
                }
            });
    cache.put(1, "init value");
    System.out.println(cache.get(1));
    cache.invalidate(1);
    System.out.println(cache.get(1));
    cache.put(2, "init value");
    cache.invalidateAll();
}

输出结果:
init value
the key 1 is removed
shanghai 1667918329448
the key 2 is removed
the key 1 is removed

Guava Cache 中还可以基于引用过期,包括weakKeys、weakValues和softValues。weakKeys的意思是清除key为,这几个引用需要慎用,示例如下。

@SneakyThrows
private static void invalidateDemo() {
    LoadingCache<Integer, String> cache = CacheBuilder.newBuilder()
            .weakKeys()
            .weakValues()
            .removalListener(removalNotification ->
                    System.out.println("the key " + removalNotification.getKey() + " is removed"))
            .build(new CacheLoader<Integer, String>() {
                @Override
                public String load(Integer integer) {
                    return getCityFromDb(integer);
                }
            });
    cache.put(3, new String("init key"));
    System.gc();
    System.out.println(cache.get(1));
}

输出结果:
the key 3 is removed
shanghai 1667918601703
2.3 其他方法

除了上述介绍的方法,Guava Cache 中还有很多其他有用的方法,接下来一一介绍。
1、recordStats,该方法用于记录缓存命中的状态,便于我们更好的使用缓存,示例如下:

@SneakyThrows
private static void otherMethodDemo() {
    LoadingCache<Integer, String> cache = CacheBuilder.newBuilder()
            .recordStats()
            .removalListener(removalNotification ->
                    System.out.println("the key " + removalNotification.getKey() + " is removed"))
            .build(new CacheLoader<Integer, String>() {
                @Override
                public String load(Integer integer) {
                    return getCityFromDb(integer);
                }
            });
    cache.put(1, "init value");
    System.out.println(cache.get(1));
    System.out.println(cache.get(2));
    System.out.println(cache.get(3));
    System.out.println(cache.stats());
}

输出结果:
init value
beijing 1667959350889
shenzhen 1667959353893
CacheStats{hitCount=1, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=6008055275, evictionCount=0}

可以看到这里缓存命中了一次(key=1),两次没有命中(key=2、3),加载成功两次(key=2、3)
2、maximumSize,该方法用于设置缓存的最大容量,注意这里是个数,并不是内存大小。当缓存个数大于设置的最大容量时,则会使用LRU算法(通过concurrentHashMap和双向链表实现)来淘汰缓存。

3、Caffine Cache 的使用

Caffeine 是一款基于Java8开发的,高性能的的本地缓存库,出自于Benjamin Manes大神之手,大家熟悉的Spring4.3+和SpringBoot1.4+缓存的实现便是基于Caffeine。Caffeine 的缓存淘汰算法是一种对LRU和LFU进行了组合优化的算法,caffeine的用法与guava基本相同,有一些不同的方法。
除了这两种初始化方式外,caffeine cache还提供了第三种初始化方式,异步加载方式
文章来源地址https://www.toymoban.com/news/detail-454002.html

到了这里,关于十分钟掌握Java本地缓存的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【鸟哥杂谈】十分钟搭建自己的本地 Node-Red可拖拽图形化物联网

    忘记过去,超越自己 ❤️ 博客主页 单片机菜鸟哥,一个野生非专业硬件IOT爱好者 ❤️ ❤️ 本篇创建记录 2022-10-16 ❤️ ❤️ 本篇更新记录 2022-10-16 ❤️ 🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝 🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请留

    2024年02月11日
    浏览(48)
  • 十分钟教你学会JAVA中的学生管理系统

      目录 创建学生类 测试类中主界面的代码编写 添加学生信息的代码编写 (addStuent) 删除学生信息的代码编写 (deleteStudent) 修改学生信息的代码编写 (updateStudent) 查看学生信息的代码编写(findAllStudent) 检索每个类中的学号代码编写(nameUsed) 学生系统的简要概括:通过Array L

    2024年02月13日
    浏览(66)
  • 十分钟入门Zigbee

    大部分教程通常都是已Zigbee原理开始讲解和学习,各种概念让初学者难以理解。本教程从一个小白的角度出发,入门无需任何Zigbee底层原理知识,只需要基本的MCU研发经验就可以掌握,让您快速实现zigbee组网和节点之间通信。 本教程采用泰凌微TLSR8258芯片,芯片资料链接TLS

    2023年04月09日
    浏览(75)
  • 每天十分钟学会Spark

    Spark是什么 Spark是一种基于内存的快速、通用、可拓展的大数据分析计算引擎。 Spark官网:http://spark.apache.org/ Spark的特点 1、快速   一般情况下,对于迭代次数较多的应用程序,Spark程序在内存中的运行速度是Hadoop MapReduce运行速度的100多倍,在磁盘上的运行速度是Hadoop MapRedu

    2024年03月18日
    浏览(68)
  • Django入门,十分钟学会登录网页

    我们假定你已经阅读了 安装 Django。你能知道 Django 已被安装,且安装的是哪个版本,通过在命令提示行输入命令 cmd黑窗口运行,不懂cmd百度一下 如果这是你第一次使用 Django 的话,你需要一些初始化设置。也就是说,你需要用一些自动生成的代码配置一个 Django project ——

    2024年01月24日
    浏览(70)
  • 十分钟理解回归测试(Regression Testing)

    回归测试是一个系统的质量控制过程,用于验证最近对软件的更改或更新是否无意中引入了新错误或对以前的功能方面产生了负面影响(比如你在家中安装了新的空调系统,发现虽然新的空调系统可以按预期工作,但是本来亮的等却不亮了)。其主要目标是确保旨在改进的修

    2024年02月05日
    浏览(82)
  • 十分钟python入门 正则表达式

    正则常见的三种功能,它们分别是:校验数据的有效性、查找符合要求的文本以及对文本进行切割和替换等操作。 所谓元字符就是指那些在正则表达式中具有特殊意义的专用字符 元字符大致分成这几类:表示单个特殊字符的,表示空白符的,表示某个范围的,表示次数的量

    2024年02月13日
    浏览(58)
  • 十分钟读懂Stable Diffusion运行原理

    AIGC 热潮正猛烈地席卷开来,可以说 Stable Diffusion 开源发布把 AI 图像生成提高了全新高度,特别是 ControlNet 和 T2I-Adapter 控制模块的提出进一步提高生成可控性,也在逐渐改变一部分行业的生产模式。惊艳其出色表现,也不禁好奇其背后技术。本文整理了一些学习过程中记录的

    2024年02月09日
    浏览(70)
  • 十分钟玩转3D绘图:WxGL完全手册

    WxGL是一个基于PyOpenGL的跨平台三维数据快速可视化工具包,提供类似Matplotlib风格的应用方式。WxGL也可以集成到wxPython或PyQt6中实现更多的功能和控制。 WxGL提供了一套简洁易用、对用户友好的API,将OpenGL的复杂概念封装起来,使得用户可以更加专注于数据的处理,而无需在3

    2024年01月22日
    浏览(73)
  • 十分钟实现 Android Camera2 相机预览

    因为工作中要使用 Android Camera2 API ,但因为 Camera2 比较复杂,网上资料也比较乱,有一定入门门槛,所以花了几天时间系统研究了下,并在 CSDN 上记录了下,希望能帮助到更多的小伙伴。 Camera2 API 的包名是 android.hardware.camera2 ,是 Android 5.0 后推出的一套调用摄像头设备的接口

    2024年02月13日
    浏览(73)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包