此文已由作者赵计刚授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。

1、guava cache

  • 当下最常用最简单的本地缓存

  • 线程安全的本地缓存

  • 类似于ConcurrentHashMap(或者说成就是一个ConcurrentHashMap,只是在其上多添加了一些功能)

2、使用实例

具体在实际中使用的例子,去查看《第七章 企业项目开发--本地缓存guava cache》,下面只列出测试实例:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; public class Hello{
    
    LoadingCache<String, String> testCache = CacheBuilder.newBuilder()
            .expireAfterWrite(20, TimeUnit.MINUTES)// 缓存20分钟
            .maximumSize(1000)// 最多缓存1000个对象
            .build(new CacheLoader<String, String>() {
                public String load(String key) throws Exception {
                    if(key.equals("hi")){
                        return null;
                    }
                    return key+"-world";
                }
            });
    
    public static void main(String[] args){
        Hello hello = new Hello();
        System.out.println(hello.testCache.getIfPresent("hello"));//null
        hello.testCache.put("123", "nana");//存放缓存
        System.out.println(hello.testCache.getIfPresent("123"));//nana
        try {
            System.out.println(hello.testCache.get("hello"));//hello-world
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(hello.testCache.getIfPresent("hello"));//hello-world
        /***********测试null*************/
        System.out.println(hello.testCache.getIfPresent("hi"));//null
        try {
            System.out.println(hello.testCache.get("hi"));//抛异常
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        
    }
}

在这个方法中,基本已经覆盖了guava cache常用的部分。

  • 构造缓存器

    • 缓存器的构建没有使用构造器而不是使用了构建器模式,这是在存在多个可选参数的时候,最合适的一种配置参数的方式,具体参看《effective Java(第二版)》第二条建议。

  • 常用的三个方法

    • get(Object key)

    • getIfPresent(Object key)

    • put(Object key, Object value)

3、源代码

在阅读源代码之前,强烈建议,先看一下"Java并发包类源码解析"中的《第二章 ConcurrentHashMap源码解析》,链接如下:

http://www.cnblogs.com/java-zhao/p/5113317.html

对于源码部分,由于整个代码的核心类LocalCache有5000多行,所以只介绍上边给出的实例部分的相关源码解析。本节只说一下缓存器的构建,即如下代码部分:

    LoadingCache<String, String> testCache = CacheBuilder.newBuilder()
            .expireAfterWrite(20, TimeUnit.MINUTES)// 缓存20分钟(时间起点:entry的创建或替换(即修改))
            //.expireAfterAccess(10, TimeUnit.MINUTES)//缓存10分钟(时间起点:entry的创建或替换(即修改)或最后一次访问)
            .maximumSize(1000)// 最多缓存1000个对象
            .build(new CacheLoader<String, String>() {
                public String load(String key) throws Exception {
                    if(key.equals("hi")){
                        return null;
                    }
                    return key+"-world";
                }
            });

说明:该代码的load()方法会在之后将get(Object key)的时候再说,这里先不说了。

对于这一块儿,由于guava cache这一块儿的代码虽然不难,但是容易看的跑偏,一会儿就不知道跑到哪里去了,所以我下边先给出guava cache的数据结构以及上述代码的执行流程,然后大家带着这个数据结构和执行流程去分析下边的源代码,分析完源代码之后,我在最后还会再将cache的数据结构和构建缓存器的执行流程给出,并会结合我们给出的开头实例代码来套一下整个流程,最后画出初始化构建出来的缓存器(其实,这个缓存器就是上边以及文末给出的cache的数据结构图)。

guava cache的数据结构图:


需要说明的是:

  • 每一个Segment中的有效队列(废弃队列不算)的个数最多可能不止一个

  • 上图与ConcurrentHashMap及其类似,其中的ReferenceEntry[i]用于存放key-value

  • 每一个ReferenceEntry[i]都会存放一个链表,当然采用的也是Entry替换的方式。

  • 队列用于实现LRU缓存回收算法

  • 多个Segment之间互不打扰,可以并发执行

  • 各个Segment的扩容只需要扩自己的就好,与其他Segment无关

  • 根据需要设置好初始化容量与并发水平参数,可以有效避免扩容带来的昂贵代价,但是设置的太大了,又会耗费很多内存,要衡量好

后边三条与ConcurrentHashMap一样

guava cache的数据结构的构建流程:

1)构建CacheBuilder实例cacheBuilder

2)cacheBuilder实例指定缓存器LocalCache的初始化参数

3)cacheBuilder实例使用build()方法创建LocalCache实例(简单说成这样,实际上复杂一些)

3.1)首先为各个类变量赋值(通过第二步中cacheBuilder指定的初始化参数以及原本就定义好的一堆常量)

3.2)之后创建Segment数组

3.3)最后初始化每一个Segment[i]

3.3.1)为Segment属性赋值

3.3.2)初始化Segment中的table,即一个ReferenceEntry数组(每一个key-value就是一个ReferenceEntry)

3.3.3)根据之前类变量的赋值情况,创建相应队列,用于LRU缓存回收算法

类结构:(这个不看也罢)

  • CacheBuilder:设置LocalCache的相关参数,并创建LocalCache实例

  • CacheLoader:有用的部分就是一个load(),用于实现"取缓存-->若不存在,先计算,在缓存-->取缓存"的原子操作

  • LocalCache:整个guava cache的核心类,包含了guava cache的数据结构以及基本的缓存的操作方法

  • LocalLoadingCache:LocalCache的一个静态内部类,这里的get(K key)是外部调用get(K key)入口

  • LoadingCache接口:继承于Cache接口,定义了get(K key)

  • Cache接口:定义了getIfPresent(Object key)和put(K key, V value)

  • LocalManualCache:LocalCache的一个静态内部类,是LocalLoadingCache的父类,这里的getIfPresent(Object key)和put(K key, V value)也是外部方法的入口

关于上边的这些说明,结合之后的源码进行看就好了。

注:如果在源码中有一些注释与最后的套例子的注释不同的话,以后者为准

3.1、构建CacheBuilder+为LocalCache设置相关参数+创建LocalCache实例

CacheBuilder的一些属性:

    private static final int DEFAULT_INITIAL_CAPACITY = 16;//用于计算每个Segment中的hashtable的大小
    private static final int DEFAULT_CONCURRENCY_LEVEL = 4;//用于计算有几个Segment
    private static final int DEFAULT_EXPIRATION_NANOS = 0;//默认的缓存过期时间
    
    static final int UNSET_INT = -1;
    
    int initialCapacity = UNSET_INT;//用于计算每个Segment中的hashtable的大小
    int concurrencyLevel = UNSET_INT;//用于计算有几个Segment
    long maximumSize = UNSET_INT;//cache中最多能存放的缓存entry个数
    long maximumWeight = UNSET_INT;
    
    Strength keyStrength;//键的引用类型(strong、weak、soft)
    Strength valueStrength;//值的引用类型(strong、weak、soft)     long expireAfterWriteNanos = UNSET_INT;//缓存超时时间(起点:缓存被创建或被修改)
    long expireAfterAccessNanos = UNSET_INT;//缓存超时时间(起点:缓存被创建或被修改或被访问)

CacheBuilder-->newCacheBuilder():创建一个CacheBuilder实例

    /**
     * 采用默认的设置(如下)创造一个新的CacheBuilder实例
     * 1、strong keys
     * 2、strong values
     * 3、no automatic eviction of any kind.
     */
    public static CacheBuilder<Object, Object> newBuilder() {
        return new CacheBuilder<Object, Object>();//new 一个实例
    }

接下来,使用构建器模式指定一些属性值(这里的话,就是超时时间:expireAfterWriteNanos+cache中最多能放置的entry个数:maximumSize),这里的entry指的就是一个缓存(key-value对)

CacheBuilder-->expireAfterWrite(long duration, TimeUnit unit)

    /**
     * 指明每一个entry(key-value)在缓存中的过期时间
     * 1、时间的参考起点:entry的创建或值的修改
     * 2、过期的entry也许会被计入缓存个数size(也就是说缓存个数不仅仅只有存活的entry)
     * 3、但是过期的entry永远不会被读写
     */
    public CacheBuilder<K, V> expireAfterWrite(long duration, TimeUnit unit) {
        /*
         * 检查之前是否已经设置过缓存超时时间
         */
        checkState(expireAfterWriteNanos == UNSET_INT,//正确条件:之前没有设置过缓存超时时间
                   "expireAfterWrite was already set to %s ns",//不符合正确条件的错误信息
                   expireAfterWriteNanos);
        /*
         * 检查设置的超时时间是否大于等于0,当然,通常情况下,我们不会设置缓存为0
         */
        checkArgument(duration >= 0, //正确条件
                      "duration cannot be negative: %s %s",//不符合正确条件的错误信息,下边的是错误信息中的错误参数
                      duration, 
                      unit);
        this.expireAfterWriteNanos = unit.toNanos(duration);//根据输入的时间值与时间单位,将时间值转换为纳秒
        return this;
    }

注意:

  • 设置超时时间,注意时间的起点是entry的创建或替换(修改)

  • expireAfterAccess(long duration, TimeUnit unit)方法的时间起点:entry的创建或替换(修改)或被访问

免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐

更多网易技术、产品、运营经验分享请点击

相关文章:
【推荐】 Docker容器的原理与实践 (下)
【推荐】 金融事业部QA培训体系
【推荐】 Question|网站被黑客扫描撞库该怎么应对防范?

Google guava cache源码解析1--构建缓存器(1)的更多相关文章

  1. 第二章 Google guava cache源码解析1--构建缓存器

    1.guava cache 当下最常用最简单的本地缓存 线程安全的本地缓存 类似于ConcurrentHashMap(或者说成就是一个ConcurrentHashMap,只是在其上多添加了一些功能) ...

  2. Google guava cache源码解析1--构建缓存器(3)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 下面介绍在LocalCache(CacheBuilder, CacheLoader)中调用的一些方法: Ca ...

  3. Google guava cache源码解析1--构建缓存器(2)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. CacheBuilder-->maximumSize(long size)     /**       ...

  4. Guava Cache源码解析

    概述: 本次主要是分析cache的源码,基本概念官方简介即可. 基本类图: 在官方的文档说明中,Guava Cache实现了三种加载缓存的方式: LoadingCache在构建缓存的时候,使用buil ...

  5. [源码解析] PyTorch分布式优化器(1)----基石篇

    [源码解析] PyTorch分布式优化器(1)----基石篇 目录 [源码解析] PyTorch分布式优化器(1)----基石篇 0x00 摘要 0x01 从问题出发 1.1 示例 1.2 问题点 0 ...

  6. [源码解析] PyTorch分布式优化器(2)----数据并行优化器

    [源码解析] PyTorch分布式优化器(2)----数据并行优化器 目录 [源码解析] PyTorch分布式优化器(2)----数据并行优化器 0x00 摘要 0x01 前文回顾 0x02 DP 之 ...

  7. [源码解析] PyTorch分布式优化器(3)---- 模型并行

    [源码解析] PyTorch分布式优化器(3)---- 模型并行 目录 [源码解析] PyTorch分布式优化器(3)---- 模型并行 0x00 摘要 0x01 前文回顾 0x02 单机模型 2.1 ...

  8. Guava Cache源码详解

    目录 一.引子 二.使用方法 2.1 CacheBuilder有3种失效重载模式 2.2 测试验证 三.源码剖析 3.1 简介 3.2 源码剖析 四.总结 优点: 缺点: 正文 回到顶部 一.引子 缓 ...

  9. 常用限流算法与Guava RateLimiter源码解析

    在分布式系统中,应对高并发访问时,缓存.限流.降级是保护系统正常运行的常用方法.当请求量突发暴涨时,如果不加以限制访问,则可能导致整个系统崩溃,服务不可用.同时有一些业务场景,比如短信验证码,或者其它 ...

随机推荐

  1. 对象回收过程?线程池执行过程? map原理?集合类关系?synchronized 和 volatile ? 同一个类的方法事务传播控制还有作用吗?java 锁

    1.  对象回收过程? 可达性分析算法: 如果一个对象从 GC Roots 不可达时,则证明此对象不可用. 通过一系列称为GC ROOTS的对象作为起点,从这些起点往下搜索,搜索走过的路径 称为引用链 ...

  2. 【UI测试】--易用性

  3. netty4初步使用

    文件 D:\jp\netty\NtServer.java import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Chan ...

  4. Servet-------JSTL标签库

    JSTL标签库 也可以和EL表达式配合使用 作用:   提高在Jsp中的逻辑代码的编写效率,使用标签..(对EL表达式的扩展)   使用: JSTL的核心标签库(重点) JSTL的SQL标签库 JST ...

  5. 46.UISearchBar的placeholder字体颜色和背景颜色

    1.改变searchbar的searchField属性 UITextField *searchField = [searchbar valueForKey:@"searchField&quo ...

  6. 2019.01.08 codeforces 1009F. Dominant Indices(长链剖分)

    传送门 长链剖分模板题. 题意:给出一棵树,设fi,jf_{i,j}fi,j​表示iii的子树中距离点iii距离为jjj的点的个数,现在对于每个点iii要求出使得fif_ifi​取得最大值的那个jjj ...

  7. Tomcat架构解析(四)-----Coyote、HTTP、AJP、HTTP2等协议

    Connector是Tomcat中非常重要的一个组成部分,说白了,就是如何从客户端获取到相应的请求信息.这部分主要包括的难点有这样几个部分: 1.客户端与服务端的协议 客户端与服务端的协议是多种多样的 ...

  8. iptables说明(转)

    原文:https://www.linuxidc.com/Linux/2016-09/134832.htm 前提基础: 当主机收到一个数据包后,数据包先在内核空间中处理,若发现目的地址是自身,则传到用户 ...

  9. C++ MFC棋牌类小游戏day1

    好用没用过C++做一个完整一点的东西了,今天开始希望靠我这点微薄的技术来完成这个小游戏. 我现在的水平应该算是菜鸟中的战斗鸡了,所以又很多东西在设计和技术方面肯定会有很大的缺陷,我做这个小游戏的目的单 ...

  10. IntelliJ IDEA 2017版 spring-boot 2.0.3 邮件发送搭建,概念梳理 (一)

    邮件发送功能总结        第一部分 背景   一.使用场景 (1)注册验证    注册各大网站,通常需要输入邮件地址,在注册成功后,会发送一封邮箱验证的邮件,点击确认,证明这个邮箱是用户自己的 ...