Guava Cache 实现与源码分析

目录

  • Guava Cache

    • 一、概述

      • 1、内存缓存
      • 2、核心数据结构
    • 二、具体实现
      • 0、一览众山小
      • 1、CacheBuilder 构建器
      • 2、LocalCache

Guava Cache

一、概述

1、内存缓存

可看作一个jdk7的concurrentHashMap,核心功能get,put

但是比一般的map多了一些功能,如:

  • ??过限失效(根据不同的维度失效,读后N秒,写后N秒,最大size,最大weight)
  • 自动刷新
  • 支持软引用和弱引用
  • 监听删除

2、核心数据结构

和jdk7的HashMap相似

有N个Segment,每个Segment下是一个HashTable,每个HashTable里是一个链表

Guava的锁是一个比较重的操作,锁住的是整个Segment(Segment继承的是ReetrentLock,惊)

二、具体实现

0、一览众山小

主要的类:

CacheBuilder 设置参数,构建LoadingCache

LocalCache 是核心实现,虽然builder构建的是LocalLoadingCache(带refresh功能)和LocalManualCache(不带refresh功能),但其实那两个只是个壳子

1、CacheBuilder 构建器

提要:

记录所需参数

public final class CacheBuilder<K, V> {

    public <K1 extends K, V1 extends V> LoadingCache<K1, V1> build(
        CacheLoader<? super K1, V1> loader) { // loader是用来自动刷新的
        checkWeightWithWeigher();
    return new LocalCache.LocalLoadingCache<>(this, loader);
    }

    public <K1 extends K, V1 extends V> Cache<K1, V1> build() { // 这个没有loader,就不会自动刷新
        checkWeightWithWeigher();
        checkNonLoadingCache();
        return new LocalCache.LocalManualCache<>(this);
    }

    int initialCapacity = UNSET_INT; // 初始map大小
    int concurrencyLevel = UNSET_INT; // 并发度
    long maximumSize = UNSET_INT;
    long maximumWeight = UNSET_INT;
    Weigher<? super K, ? super V> weigher;
    Strength keyStrength; // key强、弱、软引,默认为强
    Strength valueStrength; // value强、弱、软引,默认为强
    long expireAfterWriteNanos = UNSET_INT; // 写过期
    long expireAfterAccessNanos = UNSET_INT; //
    long refreshNanos = UNSET_INT; //
    Equivalence<Object> keyEquivalence; // 强引时为equals,否则为==
    Equivalence<Object> valueEquivalence; // 强引时为equals,否则为==
    RemovalListener<? super K, ? super V> removalListener; // 删除时的监听
    Ticker ticker; // 时间钟,用来获得当前时间的
    Supplier<? extends StatsCounter> statsCounterSupplier = NULL_STATS_COUNTER; // 计数器,用来记录get或者miss之类的数据
}

2、LocalCache

1)初始化

提要:

a)赋值

b)初始化Segment[]数组

 LocalCache(
      CacheBuilder<? super K, ? super V> builder, @Nullable CacheLoader<? super K, V> loader) {

    // a)把builder的参数赋值过来,略

    // b)构建Segment[]数组,原理可参照jdk7点concurrentHashMap
    int segmentShift = 0;
    int segmentCount = 1; // 设置为刚刚好比concurrencyLevel大的2的幂次方的值
    while (segmentCount < concurrencyLevel && (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
      ++segmentShift;
      segmentCount <<= 1;
    }
    this.segmentShift = 32 - segmentShift;
    segmentMask = segmentCount - 1;

    this.segments = newSegmentArray(segmentCount);

    int segmentCapacity = initialCapacity / segmentCount; //每个Segment的容量
    int segmentSize = 1; // 刚刚好比容量大的2等幂次方的值
    while (segmentSize < segmentCapacity) {
      segmentSize <<= 1;
    }

    if (evictsBySize()) {
      // Ensure sum of segment max weights = overall max weights
      long maxSegmentWeight = maxWeight / segmentCount + 1;
      long remainder = maxWeight % segmentCount;
      for (int i = 0; i < this.segments.length; ++i) {
        if (i == remainder) {
          maxSegmentWeight--;
        }
        this.segments[i] =
            createSegment(segmentSize, maxSegmentWeight, builder.getStatsCounterSupplier().get());
      }
    } else {
      for (int i = 0; i < this.segments.length; ++i) {
        this.segments[i] =
            createSegment(segmentSize, UNSET_INT, builder.getStatsCounterSupplier().get()); // 往Segment数组里塞
      }
    }
  }

Segment(
   LocalCache<K, V> map,
   int initialCapacity,
   long maxSegmentWeight,
   StatsCounter statsCounter) {
 this.map = map;
 this.maxSegmentWeight = maxSegmentWeight;
 this.statsCounter = checkNotNull(statsCounter);
 initTable(newEntryArray(initialCapacity));
 // 当key是弱、软引用时,初始化keyReferenceQueue;其父类特性决定其gc时,会将被GC的元素放入该队列中
 keyReferenceQueue = map.usesKeyReferences() ? new ReferenceQueue<K>() : null;

 valueReferenceQueue = map.usesValueReferences() ? new ReferenceQueue<V>() : null;

 recencyQueue =
     map.usesAccessQueue()
         ? new ConcurrentLinkedQueue<ReferenceEntry<K, V>>()
         : LocalCache.<ReferenceEntry<K, V>>discardingQueue();

 writeQueue =
     map.usesWriteQueue()
         ? new WriteQueue<K, V>()
         : LocalCache.<ReferenceEntry<K, V>>discardingQueue();

 accessQueue =
     map.usesAccessQueue()
         ? new AccessQueue<K, V>()
         : LocalCache.<ReferenceEntry<K, V>>discardingQueue();
}

2)put

提要

a)找到key所在的segment,调用segment.put方法

b)锁住segment,清理

i)如果key存在

ii)如果key不存在

c)清理

class LocalCache {
    public V put(K key, V value) {
        checkNotNull(key);
        checkNotNull(value);
        int hash = hash(key); // 计算hash
        return segmentFor(hash).put(key, hash, value, false); // 找到hash所分配到的的Segment,put进去
    }
}

// 转而来看Segment的put方法
class Segment<K,V> implements ReentrantLock {
    V put(K key, int hash, V value, boolean onlyIfAbsent) {
      lock(); // 锁住一个segment
      try {
        long now = map.ticker.read(); //获得当前时间
        preWriteCleanup(now); //清除软/弱引用 详见 2.4

        int newCount = this.count + 1;
        if (newCount > this.threshold) { // 如有需要则扩容
          expand();
          newCount = this.count + 1;
        }

        AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
        int index = hash & (table.length() - 1);
        ReferenceEntry<K, V> first = table.get(index);

        // Look for an existing entry.
        // 根据不同情况决定是否要执行操作,1)count++ 更新数量 2)enqueueNotification 入队通知 3)setValue 更新值 4)evictEntries 淘汰缓存
        for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
          K entryKey = e.getKey();
          // 如果该key已经存在
          if (e.getHash() == hash
              && entryKey != null
              && map.keyEquivalence.equivalent(key, entryKey)) {
            // We found an existing entry.

            ValueReference<K, V> valueReference = e.getValueReference();
            V entryValue = valueReference.get();

            if (entryValue == null) {
              ++modCount;
              if (valueReference.isActive()) {
                enqueueNotification(
                    key, hash, entryValue, valueReference.getWeight(), RemovalCause.COLLECTED);
                setValue(e, key, value, now);
                newCount = this.count; // count remains unchanged
              } else {
                setValue(e, key, value, now);
                newCount = this.count + 1;
              }
              this.count = newCount; // write-volatile
              evictEntries(e);
              return null;
            } else if (onlyIfAbsent) {
              recordLockedRead(e, now);
              return entryValue;
            } else {
              // clobber existing entry, count remains unchanged
              ++modCount;
              enqueueNotification(
                  key, hash, entryValue, valueReference.getWeight(), RemovalCause.REPLACED);
              setValue(e, key, value, now);
              evictEntries(e);
              return entryValue;
            }
          }
        }

        // 如果该key不存在,则新建一个entry.
        ++modCount;
        ReferenceEntry<K, V> newEntry = newEntry(key, hash, first);
        setValue(newEntry, key, value, now);
        table.set(index, newEntry);
        newCount = this.count + 1;
        this.count = newCount; // write-volatile
        evictEntries(newEntry);
        return null;
      } finally {
        unlock();
        postWriteCleanup();
      }
    }

    @GuardedBy("this")
    ReferenceEntry<K, V> newEntry(K key, int hash, @Nullable ReferenceEntry<K, V> next) {
      return map.entryFactory.newEntry(this, checkNotNull(key), hash, next);
    }
  }

利用map.entryFactory创建Entry。其中entryFactory的初始化是下述得到的

EntryFactory entryFactory = EntryFactory.getFactory(keyStrength, usesAccessEntries(), usesWriteEntries());

EntryFactory是个枚举类,枚举类还可以这么用,涨知识了!

enum EntryFactory {
    STRONG {
      @Override
      <K, V> ReferenceEntry<K, V> newEntry(
          Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
        return new StrongEntry<>(key, hash, next);
      }
    },...,// 省略部分
    WEAK { // 软/弱引用的精髓!!!
      @Override
      <K, V> ReferenceEntry<K, V> newEntry(
          Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next) { // 所以!!!就是在这里!!!把这个queue放进去了,终于找到了
        return new WeakEntry<>(segment.keyReferenceQueue, key, hash, next);
      }
    }};

    // Masks used to compute indices in the following table.

    static final int ACCESS_MASK = 1;
    static final int WRITE_MASK = 2;
    static final int WEAK_MASK = 4;

    /** Look-up table for factories. */
    static final EntryFactory[] factories = {
      STRONG,
      STRONG_ACCESS,
      STRONG_WRITE,
      STRONG_ACCESS_WRITE,
      WEAK,
      WEAK_ACCESS,
      WEAK_WRITE,
      WEAK_ACCESS_WRITE,
    };

    static EntryFactory getFactory(
        Strength keyStrength, boolean usesAccessQueue, boolean usesWriteQueue) {
      int flags =
          ((keyStrength == Strength.WEAK) ? WEAK_MASK : 0)
              | (usesAccessQueue ? ACCESS_MASK : 0)
              | (usesWriteQueue ? WRITE_MASK : 0);
      return factories[flags];
    }

    // 抽象方法:创建一个entry
    abstract <K, V> ReferenceEntry<K, V> newEntry(
        Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next);
}

static class WeakEntry<K, V> extends WeakReference<K> implements ReferenceEntry<K, V> {
    WeakEntry(ReferenceQueue<K> queue, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
      super(key, queue); // 抽丝剥茧,这个是Reference的方法,所以放到这个queue里面去,是Java WeakReference类自带的功能
      this.hash = hash;
      this.next = next;
    }
}

3)get

提要

a)找到key所在的segment,调用segment.get方法

b)得到ReferenceEntry,若存在,检查value是否过期,返回结果

c)清理

class LocalCache{
    public @Nullable V get(@Nullable Object key) {
        if (key == null) {
            return null;
        }
        int hash = hash(key);
        return segmentFor(hash).get(key, hash);
    }
}

class Segment{
    V get(Object key, int hash) {
      try {
        if (count != 0) { // read-volatile
          long now = map.ticker.read();
          ReferenceEntry<K, V> e = getLiveEntry(key, hash, now); //如果发现没有找到或者过期了,则返回为null
          if (e == null) {
            return null;
          }

          V value = e.getValueReference().get();
          if (value != null) {
            recordRead(e, now);
            return scheduleRefresh(e, e.getKey(), hash, value, now, map.defaultLoader);// 如果有loader且在刷新时间段中则刷新,否则跳过
          }
          tryDrainReferenceQueues(); // 这个幽灵一般的操作,难受
        }
        return null;
      } finally {
        postReadCleanup();
      }
    }
}

4)清理软/弱引用

每次put、get前后都会进行清理检查

    @GuardedBy("this")
    void preWriteCleanup(long now) { // 写前调用,其他方法类似,只是起了个不同的名字
      runLockedCleanup(now);
    }
    void runLockedCleanup(long now) { // 加锁+执行方法
      if (tryLock()) {
        try {
          drainReferenceQueues();
          expireEntries(now); // calls drainRecencyQueue
          readCount.set(0);
        } finally {
          unlock();
        }
      }
    }
    @GuardedBy("this")
    void drainReferenceQueues() { // 清空软/弱引用key和value
      if (map.usesKeyReferences()) {
        drainKeyReferenceQueue();
      }
      if (map.usesValueReferences()) {
        drainValueReferenceQueue();
      }
    }
    @GuardedBy("this")
    void drainKeyReferenceQueue() { // 清空软/弱引用key
      Reference<? extends K> ref;
      int i = 0;
      while ((ref = keyReferenceQueue.poll()) != null) {
        @SuppressWarnings("unchecked")
        ReferenceEntry<K, V> entry = (ReferenceEntry<K, V>) ref;
        map.reclaimKey(entry);
        if (++i == DRAIN_MAX) {
          break;
        }
      }
    }
}

// 之前一直没想明白的地方就是,这个keyReferenceQueue到底是什么时候被塞进去元素的???
// 需要看下创建entry的时候的操作!!!抽丝剥茧就能知道了

public class ReentrantLock implements Lock, java.io.Serializable {

    private Sync sync;

    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        abstract void lock();

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread(); // 获取当前线程
            int c = getState();
            if (c == 0) { // 无线程持有,即无锁状态
                if (compareAndSetState(0, acquires)) { // 设置持有线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) { // 如果持有者就是当前线程,perfect
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
}

原文地址:https://www.cnblogs.com/fairyfloss/p/9275982.html

时间: 2024-10-12 18:28:22

Guava Cache 实现与源码分析的相关文章

缓存框架Guava Cache部分源码分析

在本地缓存中,最常用的就是OSCache和谷歌的Guava Cache.其中OSCache在07年就停止维护了,但它仍然被广泛的使用.谷歌的Guava Cache也是一个非常优秀的本地缓存,使用起来非常灵活,功能也十分强大,可以说是当前本地缓存中最优秀的缓存框架之一.之前我们分析了OSCache的部分源码,本篇就通过Guava Cache的部分源码,来分析一下Guava Cache的实现原理. 在分析之前,先弄清数据结构的使用.之前的文章提到,OSCache使用了一个扩展的HashTable,作

Guava 源码分析之Cache的实现原理

Guava 源码分析之Cache的实现原理 前言 Google 出的 Guava 是 Java 核心增强的库,应用非常广泛. 我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Google 大牛们是如何设计的. 缓存 本次主要讨论缓存.缓存在日常开发中举足轻重,如果你的应用对某类数据有着较高的读取频次,并且改动较小时那就非常适合利用缓存来提高性能. 缓存之所以可以提高性能是因为它的读取效率很高,就像是 CPU 的 L1.L2.L3 缓存一样,级别越高相应的读取速度也会越快. 但也

Mybatis源码分析之Cache二级缓存原理 (五)

一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(ServiceProvider Interface) ,所有的MyBatis内部的Cache缓存,都应该实现这一接口 Cache的实现类中,Cache有不同的功能,每个功能独立,互不影响,则对于不同的Cache功能,这里使用了装饰者模式实现. 看下cache的实现类,如下图: 1.FIFOCache:先进

[Android]Volley源码分析(二)Cache

Cache作为Volley最为核心的一部分,Volley花了重彩来实现它.本章我们顺着Volley的源码思路往下,来看下Volley对Cache的处理逻辑. 我们回想一下昨天的简单代码,我们的入口是从构造一个Request队列开始的,而我们并不直接调用new来构造,而是将控制权反转给Volley这个静态工厂来构造. com.android.volley.toolbox.Volley: public static RequestQueue newRequestQueue(Context conte

Guava源码分析——Immutable Collections(1)

在Java中,conrrent包提供了很多线程安全的集合,但有的时候我们可以换一种方式对思考使用线程安全集合,Guava的Immutable提供了一系列不可变集合类型,不可变就使得集合成为了常量,常量必然线程安全.对于集合的不可变,除了Guava提供的Immutable Collections以外,还是有Collections.unmodifiableCollection(),而两者之间,还是有些区别的.从UML图中,可以看出,ImmutableCollection继承了AbstractColl

Spark SQL 源码分析之 In-Memory Columnar Storage 之 cache table

/** Spark SQL源码分析系列文章*/ Spark SQL 可以将数据缓存到内存中,我们可以见到的通过调用cache table tableName即可将一张表缓存到内存中,来极大的提高查询效率. 这就涉及到内存中的数据的存储形式,我们知道基于关系型的数据可以存储为基于行存储结构 或 者基于列存储结构,或者基于行和列的混合存储,即Row Based Storage.Column Based Storage. PAX Storage. Spark SQL 的内存数据是如何组织的? Spar

mybatis源码分析之cache创建

XMLMapperBuilder.java //解析<cache /> 配置元素,创建cache对象 private void cacheElement(XNode context) throws Exception {     if (context != null) {       String type = context.getStringAttribute("type", "PERPETUAL");       Class<? exten

LevelDB源码分析--Cache及Get查找流程

本打算接下来分析version相关的概念,但是在准备的过程中看到了VersionSet的table_cache_这个变量才想起还有这样一个模块尚未分析,经过权衡觉得leveldb的version相对Cache来说相对复杂,而且version虽然对整个leveldb来说实现上跟其他功能十分紧密,但是从概念上来说却相对弱很多,有点感觉是附加的功能的感觉.所以从介绍系统首先应该注意的是整个系统概念的完整性的角度说还是先分析Cache相关的功能. 我们先来看Cache的基本框架结构数据: struct

第九篇:Spark SQL 源码分析之 In-Memory Columnar Storage源码分析之 cache table

/** Spark SQL源码分析系列文章*/ Spark SQL 可以将数据缓存到内存中,我们可以见到的通过调用cache table tableName即可将一张表缓存到内存中,来极大的提高查询效率. 这就涉及到内存中的数据的存储形式,我们知道基于关系型的数据可以存储为基于行存储结构 或 者基于列存储结构,或者基于行和列的混合存储,即Row Based Storage.Column Based Storage. PAX Storage. Spark SQL 的内存数据是如何组织的? Spar