本地缓存框架:ConcurrentHashMap,Caffeine、GuavaCache、EhCache总结

一、ConcurrentHashMap

连接

二、Caffeine

介绍

        Caffeine是一个基于Java8开发的提供了近乎最佳命中率的高性能的缓存库,支持丰富的缓存过期策略,使用的是:TinyLfu淘汰算法

        caffeine的API操作功能和Guava 是基本保持一致的。并且caffeine为了兼容之前使用Guava 的用户,做了一个Guava的Adapter可供兼容。

        缓存和ConcurrentMap有点相似,区别是ConcurrentMap将会持有所有加入到缓存当中的元素,直到它们被从缓存当中手动移除。但是,Caffeine的缓存Cache 通常会被配置成自动驱逐缓存中元素,以限制其内存占用。


    Caffeine 关联的缓存有三种:Cache,LoadingCache 和 AsyncLoadingCache。     springboot + CaffeineCacheManager 所使用的是 LoadingCache。


Caffeine 配置说明


参数

类型

描述

initialCapacity

int

初始的缓存空间大小

maximumSize

long

缓存的最大条数

maximumWeight

long

缓存的最大权重

expireAfterWrite或expireAfterAccess

duration

最后一次写入或访问后经过固定时间过期

refreshAfterWrite

duration

创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存

weakKeys

boolean

打开 key 的弱引用

weakValues

boolean

打开 value 的弱引用

softValues

boolean

打开 value 的软引用

recordStats

开发统计功能

备注:

  • weakValues 和 softValues 不可以同时使用。
  • maximumSize 和 maximumWeight 不可以同时使用。
  • expireAfterWrite 和 expireAfterAccess 同事存在时,以 expireAfterWrite 为准。

特点:

(1)缓存淘汰策略:

        提供了三种缓存淘汰策略,分别是基于大小、权重、时间、引用方式、手动清除。

基于大小的方式:

1、可以使用Caffeine.maximumSize(long)方法来指定缓存的最大容量。

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .maximumSize(10_000)
    .build();

2、可以使用权重的策略来进行驱逐,

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .maximumWeight(10_000)
    .build();


Caffeine<String, Person> caffeine = Caffeine.newBuilder()
     .maximumWeight(30)
     .weigher((String key, Person value)-> value.getAge());

3.基于时间的方式:
1、expireAfterAccess(long, TimeUnit):在最后一次访问或者写入后开始计时,在指定的时间后过期。假如一直有请求访问该key,那么这个缓存将一直不会过期

2、expireAfterWrite(long, TimeUnit): 在最后一次写入缓存后开始计时,在指定的时间后过期

3、expireAfter(Expiry): 自定义策略,过期时间由Expiry实现独自计算。

Caffeine
                .newBuilder()
                // 二十分钟之内没有被写入,则回收
                .expireAfterWrite(20, TimeUnit.MINUTES)
                // 二十分钟之内没有被访问,则回收
                .expireAfterAccess(20, TimeUnit.MINUTES)
                .expireAfter(new Expiry<String, UserInfo>() {
                                 @Override
                                 public long expireAfterCreate(String s, UserInfo userInfo, long l) {
                                     if(userInfo.getAge() > 60){ //首次存入缓存后,年龄大于 60 的,过期时间为 4 秒
                                         return 4000000000L;
                                     }
                                     return 2000000000L; // 否则为 2 秒
                                 }

                                 @Override
                                 public long expireAfterUpdate(String s, UserInfo userInfo, long l, long l1) {
                                     if(userInfo.getName().equals("one")){ // 更新 one 这个人之后,过期时间为 8 秒
                                         return 8000000000L;
                                     }
                                     return 4000000000L; // 更新其它人后,过期时间为 4 秒
                                 }

                                 @Override
                                 public long expireAfterRead(String s, UserInfo userInfo, long l, long l1) {
                                     return 3000000000L; // 每次被读取后,过期时间为 3 秒
                                 }
                             }
                )
                .build();

import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class UserInfo {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;
}

4.基于引用的方式:

        Caffeine.newBuilder().weakKeys(); 使用弱引用存储键.当key没有其他引用时,缓存项可以被垃圾回收
        Caffeine.newBuilder().weakValues(); 使用弱引用存储值.当value没有其他引用时,缓存项可以被垃圾回收
        Caffeine.newBuilder().softValues(); 使用软引用存储值.按照全局最近最少使用的顺序回收。

java 本地 缓存 java本地缓存框架对比_加载

Caffeine
    .newBuilder()
    // 使用弱引用存储键.当key没有其他引用时,缓存项可以被垃圾回收
    .weakKeys()
    // 使用弱引用存储值.当value没有其他引用时,缓存项可以被垃圾回收
    .weakValues()
    // 使用软引用存储值.按照全局最近最少使用的顺序回收
    .softValues()
    .build();

注意:Caffeine.weakValues()和Caffeine.softValues()不可以一起使用。 

5.手动清除:

Cache<String, Object> cache = Caffeine.newBuilder().maximumSize(5).build();
// 单个清除
cache.invalidate("key");
// 批量清除
cache.invalidateAll(Arrays.asList("key", "key1"));
// 清空
cache.invalidateAll();

(2)加载方式

caffeine主要有3种加载方式:

  • 手动加载
  • 同步加载
  • 异步加载

手动加载Cache:

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
/**
 * Caffeine 手动加载
 */
public class CaffeineDemo {

    public static void main(String[] args) throws InterruptedException {
        Cache<String, String> cache = Caffeine.newBuilder()
                // 基于时间失效,写入之后开始计时失效
                .expireAfterWrite(2000, TimeUnit.MILLISECONDS)
                // 缓存容量
                .maximumSize(5)
                .build();

        // 使用java8 Lambda表达式声明一个方法,get不到缓存中的值调用这个方法运算、缓存、返回
        String value = cache.get("key", key -> key + "_" + System.currentTimeMillis());
        System.out.println(value);

        //让缓存到期
        Thread.sleep(2001);
        // 存在就取,不存在就返回空
        System.out.println(cache.getIfPresent("key"));
        // 重新存值
        cache.put("key", "value");
        String key = cache.get("key", keyOne -> keyOne + "_" + System.currentTimeMillis());
        System.out.println(key);
        // 获取所有值打印出来
        ConcurrentMap<String, String> concurrentMap = cache.asMap();
        System.out.println(concurrentMap);
        // 删除key
        cache.invalidate("key");
        // 获取所有值打印出来
        System.out.println(cache.asMap());
    }
}

同步加载LoadingCache:

import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @description Caffeine 同步加载
 */
public class CaffenineLoadingCacheDemo {

    public static void main(String[] args) throws InterruptedException {
        LoadingCache<String, String> cache = Caffeine.newBuilder()
                // 基于时间失效,写入之后开始计时失效
                .expireAfterWrite(2000, TimeUnit.MILLISECONDS)
                // 缓存容量
                .maximumSize(5)
                // 可以使用java8函数式接口的方式,这里其实是重写CacheLoader类的load方法
                .build(new MyLoadingCache());

        // 获取一个不存在的kay,让它去调用CacheLoader的load方法
        System.out.println(cache.get("key"));
        // 等待2秒让key失效
        TimeUnit.SECONDS.sleep(2);
        System.out.println(cache.getIfPresent("key"));
        // 批量获取key,让他批量去加载
        Map<String, String> all = cache.getAll(Arrays.asList("key1", "key2", "key3"));
        System.out.println(all);
    }

    static class MyLoadingCache implements CacheLoader {

        @Nullable
        @Override
        public Object load(@NonNull Object key) throws Exception {
            return key + "_" + System.currentTimeMillis();
        }
    }

}

异步加载AsyncLoadingCache:

import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * @description Caffeine 异步加载
 */
public class CaffeineAsyncLoadingCacheDemo {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        AsyncLoadingCache<Object, String> cache = Caffeine.newBuilder()
                // 基于时间失效,写入之后开始计时失效
                .expireAfterWrite(2000, TimeUnit.MILLISECONDS)
                // 缓存容量
                .maximumSize(5)
                // 可以使用java8函数式接口的方式,这里其实是重写CacheLoader的load方法
                .buildAsync(new MyCacheLoader());


        // 获取一个不存在的kay,让它异步去调用CacheLoader的load方法。这时候他会返回一个CompletableFuture
        CompletableFuture<String> future = cache.get("key");
        //验证
        future.thenAccept(s -> System.out.println("当前的时间为:" + System.currentTimeMillis() + " -> 异步加载的值为:" + s));

        // 睡眠2秒让它的key失效
        TimeUnit.SECONDS.sleep(2);

        // 注意:当使用getIfPresent时,也是返回的CompletableFuture
        // 因为getIfPresent从缓存中找不到是不会去运算key既不会调用(CacheLoader.load)方法
        // 所以得到的CompletableFuture可能会为null,如果想从CompletableFuture中取值的话.先判断CompletableFuture是否会为null
        CompletableFuture<String> completableFuture = cache.getIfPresent("key");
        if (Objects.nonNull(completableFuture)) {
            System.out.println(completableFuture.get());
        }
    }
    static  class MyCacheLoader implements CacheLoader{

        @Nullable
        @Override
        public Object load(@NonNull Object key) throws Exception {
            return key + "_" + System.currentTimeMillis();
        }
    }

}

(3)更新策略

   更新策略:在设定多长时间后会自动刷新缓存。

LoadingCache<String, String> build = Caffeine.newBuilder().
                  //1s后刷新缓存
                  refreshAfterWrite(1, TimeUnit.SECONDS).build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) {
                        // TODO 此处可以查询数据库 更新该key对应的value数据
                        return "1111";
                    }
         });

(4)缓存状态监控

        默认是使用Caffeine自带的,也可以自己进行实现。在StatsCounter接口中,定义了需要打点的方法:

  1. recordHits:记录缓存命中
  2. recordMisses:记录缓存未命中
  3. recordLoadSuccess:记录加载成功(指的是CacheLoader加载成功)
  4. recordLoadFailure:记录加载失败
  5. recordEviction:记录淘汰数据

默认: 

public class CaffeineStatsCounterDemo {
    public static void main(String[] args) throws InterruptedException {
        Cache<String, String> cache = Caffeine.newBuilder()
                // 基于时间失效,写入之后开始计时失效
                .expireAfterWrite(2000, TimeUnit.MILLISECONDS)
                // 缓存容量
                .maximumSize(5)
                .recordStats()
                .build();
        cache.put("key", "value");
        CacheStats cacheStats = cache.stats();
        System.out.println(cacheStats.toString());
    }
}

 自定义:

import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import com.github.benmanes.caffeine.cache.stats.StatsCounter;
import lombok.Data;

import java.util.concurrent.atomic.LongAdder;

/**
 * 自定义的缓存状态收集器
 */
@Data
public class MyStatsCounter implements StatsCounter {

    private final LongAdder hitCount = new LongAdder();
    private final LongAdder missCount = new LongAdder();
    private final LongAdder loadSuccessCount = new LongAdder();
    private final LongAdder loadFailureCount = new LongAdder();
    private final LongAdder totalLoadTime = new LongAdder();
    private final LongAdder evictionCount = new LongAdder();
    private final LongAdder evictionWeight = new LongAdder();

    public MyStatsCounter() {
    }

    @Override
    public void recordHits(int i) {
        hitCount.add(i);
        System.out.println("命中次数:" + i);
    }

    @Override
    public void recordMisses(int i) {
        missCount.add(i);
        System.out.println("未命中次数:" + i);
    }

    @Override
    public void recordLoadSuccess(long l) {
        loadSuccessCount.increment();
        totalLoadTime.add(l);
        System.out.println("加载成功次数:" + l);
    }

    @Override
    public void recordLoadFailure(long l) {
        loadFailureCount.increment();
        totalLoadTime.add(l);
        System.out.println("加载失败次数:" + l);
    }

    @Override
    public void recordEviction() {
        evictionCount.increment();
        System.out.println("因为缓存权重限制,执行了一次缓存清除工作");
    }

    @Override
    public void recordEviction(int weight, RemovalCause cause) {
        evictionCount.increment();
        evictionWeight.add(weight);
        System.out.println("因为缓存权重限制,执行了一次缓存清除工作,清除的数据的权重为:" + weight);
    }

    @Override
    public void recordEviction(int weight) {
        evictionCount.increment();
        evictionWeight.add(weight);
        System.out.println("因为缓存权重限制,执行了一次缓存清除工作,清除的数据的权重为:" + weight);
    }

    @Override
    public CacheStats snapshot() {
        return CacheStats.of(
                negativeToMaxValue(hitCount.sum()),
                negativeToMaxValue(missCount.sum()),
                negativeToMaxValue(loadSuccessCount.sum()),
                negativeToMaxValue(loadFailureCount.sum()),
                negativeToMaxValue(totalLoadTime.sum()),
                negativeToMaxValue(evictionCount.sum()),
                negativeToMaxValue(evictionWeight.sum()));
    }
    private static long negativeToMaxValue(long value) {
        return (value >= 0) ? value : Long.MAX_VALUE;
    }
}




MyStatsCounter myStatsCounter = new MyStatsCounter();
        Cache<String, String> cache = Caffeine.newBuilder()
                // 基于时间失效,写入之后开始计时失效
                .expireAfterWrite(2000, TimeUnit.MILLISECONDS)
                // 缓存容量
                .maximumSize(5)
                .recordStats(()->myStatsCounter)
                .build();
        cache.put("one", "one");
        cache.put("two", "two");
        cache.put("three","three");
        cache.getIfPresent("ww");
        CacheStats stats = myStatsCounter.snapshot();
        Thread.sleep(1000);
        System.out.println(stats.toString());

(5)淘汰监听

Caffeine<String, UserInfo> caffeine = Caffeine.newBuilder()
 .maximumWeight(30)
.removalListener((String key, UserInfo value, RemovalCause cause)->{
                    System.out.println("被清除人的年龄:" + value.getAge() + ";  清除的原因是:" + cause);
  }).weigher((String key, UserInfo value)-> value.getAge());
 Cache<String, UserInfo> cache = caffeine.build();
 cache.put("one", new UserInfo(12, "1"));
 cache.put("two", new UserInfo(18, "2"));
 cache.put("one", new UserInfo(14, "3"));
 cache.invalidate("one");
 cache.put("three", new UserInfo(31, "three"));
 Thread.sleep(2000);

在Caffeine中被淘汰的原因有很多种:

  1. EXPLICIT: 这个原因是,用户造成的,通过调用remove方法从而进行删除。
  2. REPLACED: 更新的时候,其实相当于把老的value给删了。
  3. COLLECTED: 用于我们的垃圾收集器,也就是我们上面减少的软引用,弱引用。
  4. EXPIRED:过期淘汰。
  5. SIZE: 大小淘汰,当超过最大的时候就会进行淘汰。

原理分析:

        Java 进程内存是有限的,不可能无限地往里面放缓存对象。这就需要有合适的淘汰算法淘汰无用的对象,为新进的对象留有空间。常见的缓存淘汰算法有 FIFO、LRU、LFU

优先淘汰掉最久未访问到的数据。缺点是不能很好地应对偶然的突发流量。比如一个数据在一分钟内的前59秒访问很多次,而在最后1秒没有访问,但是有一批冷门数据在最后一秒进入缓存,那么热点数据就会被冲刷掉。

java 本地 缓存 java本地缓存框架对比_java 本地 缓存_02

        FIFO(First In First Out):先进先出。它是优先淘汰掉最先缓存的数据、是最简单的淘汰算法。缺点是如果先缓存的数据使用频率比较高的话,那么该数据就不停地进进出出,因此它的缓存命中率比较低。

java 本地 缓存 java本地缓存框架对比_加载_03

优先淘汰掉最不经常使用的数据,需要维护一个表示使用频率的字段。主要有两个缺点:一、大多数据访问频率比较高,内存堪忧;二、无法合理更新新上的热点数据,比如某个数据历史较多,新旧数据一起需要操作,那内存堪忧。

java 本地 缓存 java本地缓存框架对比_java 本地 缓存_04

        Caffeine采用了一种结合LRU、LFU优点的算法:W-TinyLFU,其特点:高命中率、低内存占用。是一种为了解决传统LFU算法空间存储比较大的问题LFU算法,它可以在较大访问量的场景下近似的替代LFU的数据统计部分,它的原理有些类似BloomFilter

        BloomFilter原理:在BloomFilter中,使用一个大的bit数组用于存储所有key,每一个key通过多次不同的hash计算来映射数组的不同bit位,如果key存在将对应的bit位设置为1,这样就可以通过少量的存储空间进行大量的数据过滤。

        在TinyLFU中,把多个bit位看做一个整体,用于统计一个key的使用频率,TinyFLU中的key也是通过多次不同的hash计算来映射多个不同的bit组。在读取时,取映射的所有值中的最小的值作为key的使用频率。

        TinyLFU根据最大数据量设置生成一个long数组,然后将频率值保存在其中的四个long的4个bit位中(4个bit位不会大于15),取频率值时则取四个中的最小一个。

        Caffeine认为频率大于15已经很高了,是属于热数据,所以它只需要4个bit位来保存,long有8个字节64位,这样可以保存16个频率。取hash值的后左移两位,然后加上hash四次,这样可以利用到16个中的13个,利用率挺高的,或许有更好的算法能将16个都利用到。

过程说明:

  • 假设有四个hash函数,每当元素被访问时,将进行次数加1;
  • 此时会按照约定好的四个hash函数进行hash计算找到对应的位置,相应的位置进行+1操作;
  • 当获取元素的频率时,同样根据hash计算找到4个索引位置;
  • 取得四个位置的频率信息,然后根据Count Min取得最低值作为本次元素的频率值返回,即Min(Count);

java 本地 缓存 java本地缓存框架对比_加载_05

 使用方式:

一、方式一

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
@EnableCaching
@Configuration
public class CaffeineCacheConfig {
    /**
     * 在 springboot 中使用 CaffeineCacheManager 管理器管理 Caffeine 类型的缓存,Caffeine 类似 Cache 缓存的工厂,
     * 可以生产很多个 Cache 实例,Caffeine 可以设置各种缓存属性,这些 Cache 实例都共享 Caffeine 的缓存属性。
     * @return
     */
    @Bean(name = "caffeineCacheManager")
    public CacheManager oneHourCacheManager(){
        Caffeine caffeine = Caffeine.newBuilder()
                .initialCapacity(10) //初始大小
                .maximumSize(11)  //最大大小
                //写入/更新之后1小时过期
                .expireAfterWrite(1, TimeUnit.HOURS);

        CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
        caffeineCacheManager.setAllowNullValues(true);
        caffeineCacheManager.setCaffeine(caffeine);
        return caffeineCacheManager;
    }
}
@RestController
public class CaffeineController {

    @Autowired
    private CaffeineService caffeineService;

    @GetMapping("/cache-Data/{key}")
    public String cacheDataL(@PathVariable String key) {

        return caffeineService.cacheData(key);
    }

    @GetMapping("/cache-put-Data/{key}")
    public String cachePutDataL(@PathVariable String key) {

        return caffeineService.cachePutData(key);
    }


    @GetMapping("/cache-delete-Data/{key}")
    public String cacheDelteDataL(@PathVariable String key) {

        return caffeineService.deleteCaffeineServiceTest(key);
    }

    @GetMapping("/getCaffeineServiceTest")
    public String getCaffeineServiceTest(String name,Integer age) {

        return caffeineService.getCaffeineServiceTest(name,age);
    }

}
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

/**
 * @Cacheable 触发缓存入口(这里一般放在创建和获取的方法上)
 * @CacheEvict 触发缓存的eviction(用于删除的方法上)
 * @CachePut 更新缓存且不影响方法执行(用于修改的方法上,该注解下的方法始终会被执行)
 * @Caching 将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解)
 * @CacheConfig 在类级别设置一些缓存相关的共同配置(与其它缓存配合使用)
 */
@Service
@CacheConfig(cacheManager = "caffeineCacheManager")
@Slf4j
public class CaffeineService {

    @Cacheable(value = "data", key = "#key")
    public String cacheData(String key) {
        log.info("cacheData()方法执行");
        return getCache(key);
    }

    @CachePut(value = "data", key = "#key")
    public String cachePutData(String key) {
        log.info("cachePutData()方法执行");
        return "cachePutData--" + key;
    }

    private String getCache(String key) {
        try {
            log.info("getCache()方法执行");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return key;
    }

    /**
     * CacheEvict删除key,会调用cache的evict
     * */
    @CacheEvict(cacheNames = "outLimit",key = "#name")
    public String deleteCaffeineServiceTest(String name){
        String value = name + " nihao";
        log.info("deleteCaffeineServiceTest value = {}",value);
        return value;
    }

    /**
     * condition条件判断是否要走缓存,无法使用方法中出现的值(返回结果等),条件为true放入缓存
     * unless是方法执行后生效,决定是否放入缓存,返回true的放缓存
     * */
    @Cacheable(cacheNames = "outLimit",key = "#name",condition = "#value != null ")
    public String getCaffeineServiceTest(String name,Integer age){
        String value = name + " nihao "+ age;
        log.info("getCaffeineServiceTest value = {}",value);
        return value;
    }

}

java 本地 缓存 java本地缓存框架对比_System_06

java 本地 缓存 java本地缓存框架对比_加载_07

二、方式二

<dependency>
   <groupId>com.github.ben-manes.caffeine</groupId>
   <artifactId>caffeine</artifactId>
   <version>2.9.3</version>
</dependency>
@Configuration
public class CaffeineCacheConfig {

    @Bean
    public Cache<String, Object> caffeineCache() {
        return Caffeine.newBuilder()
                // 设置最后一次写入或访问后经过固定时间过期
                .expireAfterWrite(5000, TimeUnit.SECONDS)
                // 初始的缓存空间大小
                .initialCapacity(200)
                // 缓存的最大条数
                .maximumSize(2000)
                .build();
    }
}
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class UserInfo {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;

    public UserInfo(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}
import com.caffeine.lean.entity.UserInfo;
import com.caffeine.lean.utils.CaffeineUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 用户模块接口
 */
@Service
@Slf4j
public class UserInfoService {

    //模拟数据库存储数据
    private Map<Integer, UserInfo> userInfoMap = new ConcurrentHashMap<>();

    @Autowired
    private CaffeineUtils caffeineUtils;

    public void addUserInfo(UserInfo userInfo) {
        log.info("create");
        userInfoMap.put(userInfo.getId(), userInfo);
        // 加入缓存
        caffeineUtils.putAndUpdateCache(String.valueOf(userInfo.getId()), userInfo);
    }

    public UserInfo getByName(Integer userId) {
        // 先从缓存读取
        UserInfo userInfo = caffeineUtils.getObjCacheByKey(String.valueOf(userId), UserInfo.class);
        if (userInfo != null) {
            return userInfo;
        }
        // 如果缓存中不存在,则从库中查找
        log.info("get");
        userInfo = userInfoMap.get(userId);
        // 如果用户信息不为空,则加入缓存
        if (userInfo != null) {
            caffeineUtils.putAndUpdateCache(String.valueOf(userInfo.getId()), userInfo);
        }
        return userInfo;
    }

    public UserInfo updateUserInfo(UserInfo userInfo) {
        log.info("update");
        if (!userInfoMap.containsKey(userInfo.getId())) {
            return null;
        }
        // 取旧的值
        UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());
        // 替换内容
        if (StringUtils.isNotBlank(oldUserInfo.getName())) {
            oldUserInfo.setName(userInfo.getName());
        }
        if (StringUtils.isNotBlank(oldUserInfo.getSex())) {
            oldUserInfo.setSex(userInfo.getSex());
        }
        oldUserInfo.setAge(userInfo.getAge());
        // 将新的对象存储,更新旧对象信息
        userInfoMap.put(oldUserInfo.getId(), oldUserInfo);
        // 替换缓存中的值
        caffeineUtils.putAndUpdateCache(String.valueOf(oldUserInfo.getId()), oldUserInfo);
        return oldUserInfo;
    }

    public void deleteById(Integer id) {
        log.info("delete");
        userInfoMap.remove(id);
        // 从缓存中删除
        caffeineUtils.removeCacheByKey(String.valueOf(id));
    }

}
**
 * Caffeine缓存工具类
 * Cache 可以有的操作
 * V getIfPresent(K key) :如果缓存中 key 存在,则获取 value,否则返回 null。
 * void put( K key, V value):存入一对数据 <key, value>。
 * Map<K, V> getAllPresent(Iterable<?> var1) :参数是一个迭代器,表示可以批量查询缓存。
 * void putAll( Map<? extends K, ? extends V> var1); 批量存入缓存。
 * void invalidate(K var1):删除某个 key 对应的数据。
 * void invalidateAll(Iterable<?> var1):批量删除数据。
 * void invalidateAll():清空缓存。
 * long estimatedSize():返回缓存中数据的个数。
 * CacheStats stats():返回缓存当前的状态指标集。
 * ConcurrentMap<K, V> asMap():将缓存中所有的数据构成一个 map。
 * void cleanUp():会对缓存进行整体的清理,比如有一些数据过期了,但是并不会立马被清除,所以执行一次 cleanUp 方法,会对缓存进行一次检查,清除那些应该清除的数据。
 * V get( K var1, Function<? super K, ? extends V> var2):第一个参数是想要获取的 key,第二个参数是函数
 **/
@Component
public class CaffeineUtils {

    @Autowired
    Cache<String, Object> caffeineCache;

    /**
     * 添加或更新缓存
     *
     * @param key
     * @param value
     */
    public void putAndUpdateCache(String key, Object value) {
        caffeineCache.put(key, value);
    }


    /**
     * 获取对象缓存
     *
     * @param key
     * @return
     */
    public <T> T getObjCacheByKey(String key, Class<T> t) {
        caffeineCache.getIfPresent(key);
        return (T) caffeineCache.asMap().get(key);
    }

    /**
     * 根据key删除缓存
     *
     * @param key
     */
    public void removeCacheByKey(String key) {
        // 从缓存中删除
        caffeineCache.asMap().remove(key);
    }
}
import com.caffeine.lean.entity.UserInfo;
import com.caffeine.lean.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 用户模块入口
 */
@RestController
@RequestMapping
public class UserInfoController {

    @Autowired
    private UserInfoService userInfoService;

    @GetMapping("/getUserInfo/{id}")
    public Object getUserInfo(@PathVariable Integer id) {
        UserInfo userInfo = userInfoService.getByName(id);
        if (userInfo == null) {
            return "没有该用户";
        }
        return userInfo;
    }

    @PostMapping("/createUserInfo")
    public Object createUserInfo(@RequestBody UserInfo userInfo) {
        userInfoService.addUserInfo(userInfo);
        return "SUCCESS";
    }

    @PutMapping("/updateUserInfo")
    public Object updateUserInfo(@RequestBody UserInfo userInfo) {
        UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);
        if (newUserInfo == null) {
            return "不存在该用户";
        }
        return newUserInfo;
    }

    @DeleteMapping("/deleteUserInfo/{id}")
    public Object deleteUserInfo(@PathVariable Integer id) {
        userInfoService.deleteById(id);
        return "SUCCESS";
    }

}

java 本地 缓存 java本地缓存框架对比_java 本地 缓存_08

java 本地 缓存 java本地缓存框架对比_System_09

三、GuavaCache

        Google Guava Cache是本地缓存,提供了基于容量、时间、引用的缓存回收方式,内部实现采用LRU算法,基于引用回收很好的利用了java虚拟机的垃圾回收机制.

        设计灵感来源ConcurrentHashMap,使用多个segments方式的细粒度锁,在保证线程安全的同时,支持高并发场景需求,同时支持多种类型的缓存清理策略,包括基于容量的清理、基于时间的清理、基于引用的清理等。

        Guava Cache和ConcurrentHashMap很相似,但也不完全一样.最基本的区别是ConcurrentHashMap会一直保存所有添加的元素,直至显示地移除.Guava Cache为了限制内存占用,通常都设定为自动回收元素。

注意:springboot 2.x以上就踢出了guava cache.

使用场景

  1. 愿意消耗一些内存空间来提升速度
  2. 预料到某些键会被多次查询
  3. 缓存中存放的数据总量不会超出内存容量

功能:

        自动将节点加载至缓存结构中,当缓存的数据超过最大值时,使用LRU算法替换;它具备根据节点上一次被访问或写入时间计算缓存过期机制,缓存的key被封装在WeakReference引用中,缓存的value被封装在WeakReference或SoftReference引用中;还可以统计缓存使用过程中的命中率、异常率和命中率等统计数据。

        Guava Cache实质是一个在本地缓存KV数据的LocalCache。而LocalCache的数据结构和ConcurrentHashMap一样,都是采用分segment来细化管理HashMap中的节点Entry。不同的是LocalCahce中的ReferenceEntry节点更为复杂。

常用方法:

  1. get : 要么返回已经缓存的值,要么使用CacheLoader向缓存原子地加载新值;
  2. getUnchecked:CacheLoader 会抛异常,定义的CacheLoader没有声明任何检查型异常,则可以 getUnchecked 查找缓存;反之不能;
  3. getAll :方法用来执行批量查询;
  4. put : 向缓存显式插入值,Cache.asMap()也能修改值,但不具原子性;
  5. getIfPresent :该方法只是简单的把Guava Cache当作Map的替代品,不执行load方法;
  6. put(K key, V value) if cached, return; otherwise create, cache , and return.
  7. invalidate(Object key); 删除缓存
  8. invalidateAll(); 清楚所有的缓存,相当远map的clear操作。
  9.  long size(); 获取缓存中元素的大概个数。为什么是大概呢?元素失效之时,并不会实时的更新size,所以这里的size可能会包含失效元素。
  10. CacheStats stats(); 缓存的状态数据,包括(未)命中个数,加载成功/失败个数,总共加载时间,删除个数等。
  11. ConcurrentMap<K, V> asMap(); 将缓存存储到一个线程安全的map中。

特性:

(1) 创建缓存的方式

1.CacheLoader

//LoadingCache类型的缓存,可以使用get(K)或get(K,Callable)方法,并且如果使用的是get(K,Callable)方法,
// 当K值不存在时,使用的是Callable计算值,不走load方法计算,然后将值放入缓存。
LoadingCache<String, String> cache = CacheBuilder
        .newBuilder().maximumSize(100)
        .expireAfterWrite(100, TimeUnit.SECONDS) // 根据写入时间过期
        .build(new CacheLoader<String, String>() {
            @Override
            public String load(String key) {
                return getSchema(key);
            }
        });

private static String getSchema(String key) {
        System.out.println("load...");
        return key + "schema";
}

2.callable

//Cache类型的缓存只能使用Callable的方式get(K,Callable)方法
Cache<String, String> cache2 = CacheBuilder.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(100, TimeUnit.SECONDS) // 根据写入时间过期
        .build();
try {
    String value = cache2.get("key4", new Callable<String>() {
        @Override
        public String call() throws Exception {
            System.out.println("i am callable...");
            return "i am callable...";
        }
    });
    System.out.println(value);
} catch (ExecutionException e1) {
    e1.printStackTrace();
}
private static String getSchema(String key) {
        System.out.println("load...");
        return key + "schema";
}

2)缓存淘汰策略:

        提供了三种缓存淘汰策略,分别是基于大小、权重、时间、引用方式、手动清除。

 基于大小的方式:

1、可以使用CacheBuilder.maximumSize(long)方法来指定缓存的最大容量。

CacheBuilder.newBuilder()
                // 缓存的最大条数
                .maximumSize(1000)
                .build();

2、可以使用权重的策略来进行驱逐,

CacheBuilder.newBuilder()
   .maximumWeight(10_000)
   .build();


CacheBuilder.newBuilder()
  .maximumWeight(30)
  .weigher((String key, UserInfo value)-> value.getAge());

3.基于时间的方式:
1、expireAfterAccess(long, TimeUnit):在最后一次访问或者写入后开始计时,在指定的时间后过期。假如一直有请求访问该key,那么这个缓存将一直不会过期

2、expireAfterWrite(long, TimeUnit): 在最后一次写入缓存后开始计时,在指定的时间后过期。

注意:expireAfterWrite时guava重新加载数据时使用的是load方法,不会调用loadAll。

CacheBuilder
      .newBuilder()
      // 二十分钟之内没有被写入,则回收
      .expireAfterWrite(20, TimeUnit.MINUTES)
      // 二十分钟之内没有被访问,则回收
      .expireAfterAccess(20, TimeUnit.MINUTES)
      .build();

注:Guava Cache不会专门维护一个线程来回收这些过期的缓存项,只有在读/写访问时,才去判断该缓存项是否过期,如果过期,则会回收。而且注意,回收后会同步调用load方法来加载新值到cache中。

4.基于引用的方式:

        CacheBuilder.newBuilder().weakKeys(); 使用弱引用存储键.当key没有其他引用时,缓存项可以被垃圾回收
        CacheBuilder.newBuilder().weakValues(); 使用弱引用存储值.当value没有其他引用时,缓存项可以被垃圾回收
        CacheBuilder.newBuilder().softValues(); 使用软引用存储值.按照全局最近最少使用的顺序回收。

java 本地 缓存 java本地缓存框架对比_加载

CacheBuilder
     .newBuilder()
     // 使用弱引用存储键.当key没有其他引用时,缓存项可以被垃圾回收
     .weakKeys()
      // 使用弱引用存储值.当value没有其他引用时,缓存项可以被垃圾回收
      .weakValues()
      // 使用软引用存储值.按照全局最近最少使用的顺序回收
       .softValues().build();

注意:CacheBuilder.weakValues()和CacheBuilder.softValues()不可以一起使用。 

5.手动清除:

Cache<String, Object> cache = CacheBuilder.newBuilder().maximumSize(5).build();
// 单个清除
cache.invalidate("key");
// 批量清除
cache.invalidateAll(Arrays.asList("key", "key1"));
// 清空
cache.invalidateAll();

(3)监听

        在guava cache中移除key可以设置相应得监听操作,以便key被移除时做一些额外操作。缓存项被移除时,RemovalListener会获取移除通知[RemovalNotification],其中包含移除原因[RemovalCause]、键和值。监听有同步监听和异步监听两种 :

同步监听:

CacheLoader<String, String> cacheLoader = CacheLoader.from(String::toUpperCase);
        LoadingCache<String, String> loadingCache = CacheBuilder.newBuilder().maximumSize(4)
                .removalListener((notification) -> {
                    if (notification.wasEvicted()) {
                        RemovalCause cause = notification.getCause();
                        System.out.println("remove cacase is:" + cause.toString());
                        System.out.println("key:" + notification.getKey() + "value:" + notification.getValue());
                    }
                }).build(cacheLoader);

        loadingCache.getUnchecked("a");
        loadingCache.getUnchecked("b");
        loadingCache.getUnchecked("c");
        loadingCache.getUnchecked("d");//容量是4 吵了自动清除,监听程序清除是单线程。
        loadingCache.getUnchecked("e");

异步监听:

CacheLoader<String, String> cacheLoader = CacheLoader.from(String::toUpperCase);
       RemovalListener<String,String> listener =(notification) -> {
            if (notification.wasEvicted()) {
                RemovalCause cause = notification.getCause();
                System.out.println("remove cacase is:" + cause.toString());
                System.out.println("key:" + notification.getKey() + "value:" + notification.getValue());
            }
        };
        //同步监听
        LoadingCache<String, String> loadingCache = CacheBuilder.newBuilder().maximumSize(4)
                //异步监控
                .removalListener(RemovalListeners.asynchronous(listener, Executors.newSingleThreadExecutor()))
                .build(cacheLoader);

        loadingCache.getUnchecked("a");
        loadingCache.getUnchecked("b");
        loadingCache.getUnchecked("c");
        loadingCache.getUnchecked("d");//容量是4 吵了自动清除,监听程序清除是单线程。
        loadingCache.getUnchecked("e");

(4)统计

        CacheBuilder.recordStats()用来开启Guava Cache的统计功能。统计打开后Cache.stats()方法返回如下统计信息:

  • hitRate():缓存命中率;
  • hitMiss(): 缓存失误率;
  • loadcount() ; 加载次数;
  • averageLoadPenalty():加载新值的平均时间,单位为纳秒;
  • evictionCount():缓存项被回收的总数,不包括显式清除。
Cache<String, String> cache = CacheBuilder.newBuilder()
                // 基于时间失效,写入之后开始计时失效
                .expireAfterWrite(2000, TimeUnit.MILLISECONDS)
                // 缓存容量
                .maximumSize(5)
                .recordStats()
                .build();
        cache.put("key", "value");
        CacheStats cacheStats = cache.stats();
        System.out.println(cacheStats.toString());

(5)并发操作

GuavaCache通过设置 concurrencyLevel 使得缓存支持并发的写入和读取

LoadingCache<Long, UserInfo> build = CacheBuilder.newBuilder()
// 最大3个 同时支持CPU核数线程写缓存
.maximumSize(3).concurrencyLevel(Runtime.getRuntime().availableProcessors()).build(new CacheLoader<Long, UserInfo>() {
    @Override
    public UserInfo load(Long id) throws Exception {
        // 读取 db 数据
        return null;
    }
});

(6)更新策略

   更新策略:在设定多长时间后会自动刷新缓存。

LoadingCache<String, String> build = CacheBuilder.newBuilder().
              //1s内阻塞会返回旧数据
                refreshAfterWrite(1, TimeUnit.SECONDS).build(new CacheLoader<String, String>() {
            @Override
            public String load(String key) {
                // TODO 此处可以查询数据库 更新该key对应的value数据
                return null;
            }
        });

案例:

使用方式一、

<!--guava 依赖-->
<dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <version>27.0.1-jre</version>
</dependency>
import com.google.common.cache.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class GuavaCacheUtils {

    @Autowired
    Cache<String, Object> guavaCache;

    /**
     * 添加或更新缓存
     *
     * @param key
     * @param value
     */
    public void putAndUpdateCache(String key, Object value) {
        guavaCache.put(key, value);
    }


    /**
     * 获取对象缓存
     *
     * @param key
     * @return
     */
    public <T> T getObjCacheByKey(String key, Class<T> t) {
        //通过key获取缓存中的value,若不存在直接返回null
        guavaCache.getIfPresent(key);
        return (T) guavaCache.asMap().get(key);
    }

    /**
     * 根据key删除缓存
     *
     * @param key
     */
    public void removeCacheByKey(String key) {
        // 从缓存中删除
        guavaCache.asMap().remove(key);
    }

}
import com.guava.cache.lean.entity.UserInfo;
import com.guava.cache.lean.utils.GuavaCacheUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 用户模块接口
 */
@Service
@Slf4j
public class UserInfoService {

    //模拟数据库存储数据
    private Map<Integer, UserInfo> userInfoMap = new ConcurrentHashMap<>();

    @Autowired
    private GuavaCacheUtils guavaCacheUtils;

    public void addUserInfo(UserInfo userInfo) {
        log.info("create");
        userInfoMap.put(userInfo.getId(), userInfo);
        // 加入缓存
        guavaCacheUtils.putAndUpdateCache(String.valueOf(userInfo.getId()), userInfo);
    }

    public UserInfo getByName(Integer userId) {
        // 先从缓存读取
        UserInfo userInfo = guavaCacheUtils.getObjCacheByKey(String.valueOf(userId), UserInfo.class);
        if (userInfo != null) {
            return userInfo;
        }
        // 如果缓存中不存在,则从库中查找
        log.info("get");
        userInfo = userInfoMap.get(userId);
        // 如果用户信息不为空,则加入缓存
        if (userInfo != null) {
            guavaCacheUtils.putAndUpdateCache(String.valueOf(userInfo.getId()), userInfo);
        }
        return userInfo;
    }

    public UserInfo updateUserInfo(UserInfo userInfo) {
        log.info("update");
        if (!userInfoMap.containsKey(userInfo.getId())) {
            return null;
        }
        // 取旧的值
        UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());
        // 替换内容
        if (!StringUtils.isEmpty(oldUserInfo.getName())) {
            oldUserInfo.setName(userInfo.getName());
        }
        if (!StringUtils.isEmpty(oldUserInfo.getSex())) {
            oldUserInfo.setSex(userInfo.getSex());
        }
        oldUserInfo.setAge(userInfo.getAge());
        // 将新的对象存储,更新旧对象信息
        userInfoMap.put(oldUserInfo.getId(), oldUserInfo);
        // 替换缓存中的值
        guavaCacheUtils.putAndUpdateCache(String.valueOf(oldUserInfo.getId()), oldUserInfo);
        return oldUserInfo;
    }

    public void deleteById(Integer id) {
        log.info("delete");
        userInfoMap.remove(id);
        // 从缓存中删除
        guavaCacheUtils.removeCacheByKey(String.valueOf(id));
    }

}
import com.guava.cache.lean.entity.UserInfo;
import com.guava.cache.lean.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 用户模块入口
 */
@RestController
@RequestMapping
public class UserInfoController {

    @Autowired
    private UserInfoService userInfoService;

    @GetMapping("/getUserInfo/{id}")
    public Object getUserInfo(@PathVariable Integer id) {
        UserInfo userInfo = userInfoService.getByName(id);
        if (userInfo == null) {
            return "没有该用户";
        }
        return userInfo;
    }

    @PostMapping("/createUserInfo")
    public Object createUserInfo(@RequestBody UserInfo userInfo) {
        userInfoService.addUserInfo(userInfo);
        return "SUCCESS";
    }

    @PutMapping("/updateUserInfo")
    public Object updateUserInfo(@RequestBody UserInfo userInfo) {
        UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);
        if (newUserInfo == null) {
            return "不存在该用户";
        }
        return newUserInfo;
    }

    @DeleteMapping("/deleteUserInfo/{id}")
    public Object deleteUserInfo(@PathVariable Integer id) {
        userInfoService.deleteById(id);
        return "SUCCESS";
    }

}
import com.google.common.cache.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;

@EnableCaching
@Configuration
public class GuavaCacheConfig {
    @Bean
    public Cache<String, Object> guavaCache() {
        return CacheBuilder.newBuilder()
                // 设置最后一次写入或访问后经过固定时间过期
                .expireAfterWrite(6000, TimeUnit.SECONDS)
                // 初始的缓存空间大小
                .initialCapacity(100)
                // 缓存的最大条数
                .maximumSize(1000)
                .build();
    }

}

使用方式二、

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <!--guava 依赖-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>27.0.1-jre</version>
        </dependency>
import com.google.common.cache.*;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.guava.GuavaCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;

@EnableCaching
@Configuration
public class GuavaCacheConfig {
    @Bean(name = "guavaCacheManager")
    public CacheManager oneHourCacheManager(){
        CacheBuilder cacheBuilder = CacheBuilder.newBuilder()
                .initialCapacity(10) //初始大小
                .maximumSize(11)  //最大大小
                //写入/更新之后1小时过期
                .expireAfterWrite(1, TimeUnit.HOURS);
        //默认 使用CacheLoader
        GuavaCacheManager cacheManager = new GuavaCacheManager();
        cacheManager.setCacheBuilder(cacheBuilder);
        return cacheManager;
    }
}
import com.guava.cache.lean.entity.UserInfo;
import com.guava.cache.lean.service.GuavaCacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GuavaCacheController {
    @Autowired
    private GuavaCacheService guavaCacheService;

    @Autowired
    private CacheManager cacheManager;

    @GetMapping("/users/{name}")
    public UserInfo getUser(@PathVariable String name) {
        System.out.println("==================");
        UserInfo user = guavaCacheService.getUserByName(name);
        System.out.println(cacheManager.toString());
        return user;
    }
}
import com.guava.cache.lean.entity.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;

/**
 * @Cacheable 触发缓存入口(这里一般放在创建和获取的方法上)
 * @CacheEvict 触发缓存的eviction(用于删除的方法上)
 * @CachePut 更新缓存且不影响方法执行(用于修改的方法上,该注解下的方法始终会被执行)
 * @Caching 将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解)
 * @CacheConfig 在类级别设置一些缓存相关的共同配置(与其它缓存配合使用)
 */
@Service
@CacheConfig(cacheManager = "guavaCacheManager")
@Slf4j
public class GuavaCacheService {
    /**
     * 获取用户信息(此处是模拟的数据)
     */
    /**
     * 缓存 key 是 username 的数据到缓存 users 中,
     * 如果没有指定 key,则方法参数作为 key 保存到缓存中
     */
    @Cacheable(value = "Userdata", key = "#username")
    public UserInfo getUserByName(String username) {
        UserInfo user = getUserFromList(username);
        return user;
    }
    /**
     * 从模拟的数据集合中筛选 username 的数据
     */
    private UserInfo getUserFromList(String username) {
        List<UserInfo> userDaoList = UserDataFactory.getUserDaoList();
        for (UserInfo user : userDaoList) {
            if (Objects.equals(user.getName(), username)) {
                return user;
            }
        }
        return null;
    }

}
import com.guava.cache.lean.entity.UserInfo;
import java.util.ArrayList;
import java.util.List;
public class UserDataFactory {
    private UserDataFactory() {
    }
    private static List<UserInfo> userDtoList;
    static {
        // 初始化集合
        userDtoList = new ArrayList<>();

        UserInfo user = null;
        for (int i = 0; i < 5; i++) {
            user = new UserInfo();
            user.setName("star" + i);
            user.setAge(23);
            userDtoList.add(user);
        }
    }
    public static List<UserInfo> getUserDaoList() {
        return userDtoList;
    }
}
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;
}

 

java 本地 缓存 java本地缓存框架对比_java 本地 缓存_11

 

java 本地 缓存 java本地缓存框架对比_加载_12

四、EhCache

Ehcache:纯java进程内存缓存框架,具有快速、精干等特点。

核心组件:

  1. cache manager:缓存管理器
  2. cache:缓存管理器内可以放置若干cache,存放数据的实质,所有cache都实现了Ehcache接口
  3. element:单条缓存数据的组成单位
  4. system of record(SOR):可以取到真实数据的组件,可以是真正的业务逻辑、外部接口调用、存放真实数据的数据库等等,缓存就是从SOR中读取或者写入到SOR中去的。

特点:

  1. 快速、简单、支持多种缓存策略
  2. 支持内存和磁盘缓存数据,因为无需担心容量问题
  3. 缓存数据会在虚拟机重启的过程中写入磁盘
  4. 可以通过RMI、可插入API等方式进行分布式缓存(比较弱)
  5. 具有缓存和缓存管理器的侦听接口
  6. 支持多缓存管理实例,以及一个实例的多个缓存区域
  7. 提供Hibernate的缓存实现

java 本地 缓存 java本地缓存框架对比_缓存_13

Ehcache的缓存数据过期策略

        Ehcache采用的是懒淘汰机制,每次往缓存放入数据的时候,都会存一个时间,在读取的时候要和设置的时间做TTL比较来判断是否过期。

使用介绍:

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <!-- 指定一个文件目录,当EhCache把数据写到硬盘上时,将把数据写到这个文件目录下 -->
    <diskStore path="java.io.tmpdir"/>
 
    <!--
    cache元素的属性:
        name:缓存名称
        maxElementsInMemory:内存中最大缓存对象数
        maxElementsOnDisk:硬盘中最大缓存对象数,若是0表示无穷大
        eternal:true表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false
        overflowToDisk:true表示当内存缓存的对象数目达到了maxElementsInMemory界限后,会把溢出的对象写到硬盘缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行。
        diskSpoolBufferSizeMB:磁盘缓存区大小,默认为30MB。每个Cache都应该有自己的一个缓存区。
        diskPersistent:是否缓存虚拟机重启期数据
        diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认为120秒
        timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地处于空闲状态
        timeToLiveSeconds:设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清除。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有意义
        memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
    -->
 
    <!-- 设定缓存的默认数据过期策略 -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="10"
            timeToLiveSeconds="20"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"/>
 
    <cache name="simpleCache"
           maxElementsInMemory="1000"
           eternal="false"
           overflowToDisk="true"
           timeToIdleSeconds="10"
           timeToLiveSeconds="20"/>
 
</ehcache>

 可以看看这个文章,挺详细,并且写了关于其在分布式方面的应用

java 本地 缓存 java本地缓存框架对比_加载_14

代码