1. 简介

Guava Cache是指在JVM的内存中缓存数据,相比较于传统的数据库或redis存储,访问内存中的数据会更加高效,无网络开销。

根据Guava官网介绍,下面的这几种情况可以考虑使用Guava Cache:

1. 愿意消耗一些内存空间来提升速度。

2. 预料到某些键会被多次查询。

3. 缓存中存放的数据总量不会超出内存容量。

因此,Guava Cache特别适合存储那些访问量大、不经常变化、数据量不是很大的数据,以改善程序性能。

2. 类图

Guava Cache源码浅析-LMLPHP

Guava Cache的类图中,主要涉及了5个类:CacheBuilder、LocalCache、Segment、EntryFactory和ReferenceEntry,大部分业务逻辑都在前面三个类,依次介绍如下:

2.1 CacheBuilder

CacheBuilder是一个用于构建Cache的类,是建造者模式的一个例子,主要的方法有:

  • maximumSize(long maximumSize): 设置缓存存储的所有元素的最大个数。
  • maximumWeight(long maximumWeight): 设置缓存存储的所有元素的最大权重。
  • expireAfterAccess(long duration, TimeUnit unit): 设置元素在最后一次访问多久后过期。
  • expireAfterWrite(long duration, TimeUnit unit): 设置元素在写入缓存后多久过期。
  • concurrencyLevel(int concurrencyLevel): 设置并发水平,即允许多少线程无冲突的访问Cache,默认值是4,该值越大,LocalCache中的segment数组也会越大,访问效率越高,当然空间占用也大一些。
  • removalListener(RemovalListener<? super K1, ? super V1> listener): 设置元素删除通知器,在任意元素无论何种原因被删除时会调用该通知器。
  • setKeyStrength(Strength strength): 设置元素的key是强引用,还是弱引用,默认强引用,并且该属性也指定了EntryFactory使用是强引用还是弱引用。
  • setValueStrength(Strength strength) : 设置元素的value是强引用,还是弱引用,默认强引用。

2.2 LocalCache

 LocalCache是一个支持并发访问的Hash Map,它实现了ConcurrentMap,其内部会持有一个segment数组,元素的增删改查都是通过调用segment的对应方法来实现的,

其主要的方法有:

  • get(Object key): 查询一个key,内部实现是调用了Segment的get方法。
  • public V put(K key, V value): 添加一个对象到cache中,内部实现是调用了Segment的put方法。
  • remove(Object key) : 删除一个key,内部实现是调用了Segment的remove方法。
  • replace(K key, V value):更新一个key,内部实现是调用了Segment的update方法。

2.3 Segment

 segment是实际元素的持有者,它内部持有一个table数组,数组的每个元素又对应一个链表,链表上则保存了实际的元素,它的主要方法对应LocalCache提供的增删改查的接口,这里就不再啰嗦了。

2.4 EntryFactory

 EntryFactory是entry的创建工厂,可支持创建强引用、弱引用、强读引用、强写引用、强读写引用、弱读引用、弱写引用、弱读写引用等类型的元素。

强引用和弱引用就是java四种引用类型里面的强弱引用,默认是强引用,而读引用是指创建的元素会记录最后一次的访问时间,如果用户在CahceBuilder中调用了expireAfterAccess或者maximumWeight则会使用读引用类型的工厂,写引用类型也是同样的逻辑。

2.5 ReferenceEntry

 ReferenceEntry是元素的接口定义,它的实现类就是EntryFactory中创建的元素,包含了8种类型的元素,元素中至少包含了key、value和hash三个字段,其中hash是当前元素的hash值,如果是读引用则会多一个accessTime字段,以强引用的构造方法为例:

static class StrongEntry<K, V> extends AbstractReferenceEntry<K, V> {
    final K key;

    StrongEntry(K key, int hash, @Nullable ReferenceEntry<K, V> next) {
      this.key = key;
      this.hash = hash;
      this.next = next;
    }

    @Override
    public K getKey() {
      return this.key;
    }

    // The code below is exactly the same for each entry type.

    final int hash;
    final @Nullable ReferenceEntry<K, V> next;
    volatile ValueReference<K, V> valueReference = unset();
01-11 07:41