Java进程内缓存介绍

时间:2025-01-19 07:03:14

title: Java 进程内缓存 categories:

  • 编程
  • Java
  • 中间件
  • 缓存 tags:
  • Java
  • 中间件
  • 缓存 abbrlink: bd2603d3 date: 2022-02-17 22:34:30 permalink: /pages/9632fd/

Java 进程内缓存

关键词:ConcurrentHashMap、LRUHashMap、Guava Cache、Caffeine、Ehcache

一、ConcurrentHashMap

最简单的进程内缓存可以通过 JDK 自带的 HashMapConcurrentHashMap 实现。

适用场景:不需要淘汰的缓存数据

缺点:无法进行缓存淘汰,内存会无限制的增长。

二、LRUHashMap

可以通过**继承 LinkedHashMap 来实现一个简单的 LRUHashMap**,即可完成一个简单的 LRU (最近最少使用)算法。

缺点:

  • 锁竞争严重,性能比较低。
  • 不支持过期时间
  • 不支持自动刷新

【示例】LRUHashMap 的简单实现

class LRUCache extends LinkedHashMap {

    private final int max;
    private Object lock;

    public LRUCache(int max) {
        //无需扩容
        super((int) (max * 1.4f), 0.75ftrue);
        this.max = max;
        this.lock = new Object();
    }

    /**
     * 重写LinkedHashMap的removeEldestEntry方法即可 在Put的时候判断,如果为true,就会删除最老的
     *
     * @param eldest
     * @return
     */

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > max;
    }

    public Object getValue(Object key) {
        synchronized (lock) {
            return get(key);
        }
    }

    public void putValue(Object key, Object value) {
        synchronized (lock) {
            put(key, value);
        }
    }

    public boolean removeValue(Object key) {
        synchronized (lock) {
            return remove(key) != null;
        }
    }

    public boolean removeAll() {
        clear();
        return true;
    }

}

三、Guava Cache

Guava Cache 解决了 LRUHashMap 中的几个缺点。

Guava Cache 提供了基于容量,时间和引用的缓存回收方式。基于容量的方式内部实现采用 LRU 算法,基于引用回收很好的利用了 Java 虚拟机的垃圾回收机制。

其中的缓存构造器 CacheBuilder 采用构建者模式提供了设置好各种参数的缓存对象。缓存核心类 LocalCache 里面的内部类 Segment 与 jdk1.7 及以前的 ConcurrentHashMap 非常相似,分段加锁,减少锁竞争,并且都继承于 ReetrantLock,还有六个队列,以实现丰富的本地缓存方案。Guava Cache 对于过期的 Entry 并没有马上过期(也就是并没有后台线程一直在扫),而是通过进行读写操作的时候进行过期处理,这样做的好处是避免后台线程扫描的时候进行全局加锁。

直接通过查询,判断其是否满足刷新条件,进行刷新。

Guava Cache 缓存回收

Guava Cache 提供了三种基本的缓存回收方式。

基于容量回收

maximumSize(long):当缓存中的元素数量超过指定值时触发回收。

基于定时回收

  • expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。
  • expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。

如下文所讨论,定时回收周期性地在写操作中执行,偶尔在读操作中执行。

基于引用回收

  • CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。
  • CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。
  • CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。

Guava Cache 核心 API

CacheBuilder

缓存构建器。构建缓存的入口,指定缓存配置参数并初始化本地缓存。 主要采用 builder 的模式,CacheBuilder 的每一个方法都返回这个 CacheBuilder 知道 build 方法的调用。 注意 build 方法有重载,带有参数的为构建一个具有数据加载功能的缓存,不带参数的构建一个没有数据加载功能的缓存。

LocalManualCache

作为 LocalCache 的一个内部类,在构造方法里面会把 LocalCache 类型的变量传入,并且调用方法时都直接或者间接调用 LocalCache 里面的方法。

LocalLoadingCache

可以看到该类继承了 LocalManualCache 并实现接口 LoadingCache。 覆盖了 get,getUnchecked 等方法。

LocalCache

Guava Cache 中的核心类,重点了解。

LocalCache 的数据结构与 ConcurrentHashMap 很相似,都由多个 segment 组成,且各 segment 相对独立,互不影响,所以能支持并行操作。每个 segment 由一个 table 和若干队列组成。缓存数据存储在 table 中,其类型为 AtomicReferenceArray。

四、Caffeine

caffeine 是一个使用 JDK8 改进 Guava 缓存的高性能缓存库。

Caffeine 实现了 W-TinyLFU(LFU + LRU 算法的变种),其命中率和读写吞吐量大大优于 Guava Cache

其实现原理较复杂,可以参考你应该知道的缓存进化史

五、Ehcache

参考:Ehcache

六、进程内缓存对比

常用进程内缓存技术对比:

比较项 ConcurrentHashMap LRUMap Ehcache Guava Cache Caffeine
读写性能 很好,分段锁 一般,全局加锁