Guava 源码分析(Cache 原理【二阶段】)(下)

简介: 在上文「Guava 源码分析(Cache 原理)」中分析了 Guava Cache 的相关原理。 文末提到了回收机制、移除时间通知等内容,许多朋友也挺感兴趣,这次就这两个内容再来分析分析。 在开始之前先补习下 Java 自带的两个特性,Guava 中都有具体的应用。

Guava 的用法


Guava 就是利用了上文的两个特性来实现了引用回收移除通知


引用


可以在初始化缓存时利用:


  • CacheBuilder.weakKeys()


  • CacheBuilder.weakValues()


  • CacheBuilder.softValues()


来自定义键和值的引用关系。



在上文的分析中可以看出 Cache 中的 ReferenceEntry 是类似于 HashMap 的 Entry 存放数据的。


来看看 ReferenceEntry 的定义:


interface ReferenceEntry<K, V> {
    /**
     * Returns the value reference from this entry.
     */
    ValueReference<K, V> getValueReference();
    /**
     * Sets the value reference for this entry.
     */
    void setValueReference(ValueReference<K, V> valueReference);
    /**
     * Returns the next entry in the chain.
     */
    @Nullable
    ReferenceEntry<K, V> getNext();
    /**
     * Returns the entry's hash.
     */
    int getHash();
    /**
     * Returns the key for this entry.
     */
    @Nullable
    K getKey();
    /*
     * Used by entries that use access order. Access entries are maintained in a doubly-linked list.
     * New entries are added at the tail of the list at write time; stale entries are expired from
     * the head of the list.
     */
    /**
     * Returns the time that this entry was last accessed, in ns.
     */
    long getAccessTime();
    /**
     * Sets the entry access time in ns.
     */
    void setAccessTime(long time);
}


包含了很多常用的操作,如值引用、键引用、访问时间等。


根据 ValueReference<K, V> getValueReference(); 的实现:



具有强引用和弱引用的不同实现。


key 也是相同的道理:



当使用这样的构造方式时,弱引用的 key 和 value 都会被垃圾回收。


当然我们也可以显式的回收:


/**
   * Discards any cached value for key {@code key}.
   * 单个回收
   */
  void invalidate(Object key);
  /**
   * Discards any cached values for keys {@code keys}.
   *
   * @since 11.0
   */
  void invalidateAll(Iterable<?> keys);
  /**
   * Discards all entries in the cache.
   */
  void invalidateAll();


回调


改造了之前的例子:


loadingCache = CacheBuilder.newBuilder()
        .expireAfterWrite(2, TimeUnit.SECONDS)
        .removalListener(new RemovalListener<Object, Object>() {
            @Override
            public void onRemoval(RemovalNotification<Object, Object> notification) {
                LOGGER.info("删除原因={},删除 key={},删除 value={}",notification.getCause(),notification.getKey(),notification.getValue());
            }
        })
        .build(new CacheLoader<Integer, AtomicLong>() {
            @Override
            public AtomicLong load(Integer key) throws Exception {
                return new AtomicLong(0);
            }
        });


执行结果:


2018-07-15 20:41:07.433 [main] INFO  c.crossoverjie.guava.CacheLoaderTest - 当前缓存值=0,缓存大小=1
2018-07-15 20:41:07.442 [main] INFO  c.crossoverjie.guava.CacheLoaderTest - 缓存的所有内容={1000=0}
2018-07-15 20:41:07.443 [main] INFO  c.crossoverjie.guava.CacheLoaderTest - job running times=10
2018-07-15 20:41:10.461 [main] INFO  c.crossoverjie.guava.CacheLoaderTest - 删除原因=EXPIRED,删除 key=1000,删除 value=1
2018-07-15 20:41:10.462 [main] INFO  c.crossoverjie.guava.CacheLoaderTest - 当前缓存值=0,缓存大小=1
2018-07-15 20:41:10.462 [main] INFO  c.crossoverjie.guava.CacheLoaderTest - 缓存的所有内容={1000=0}


可以看出当缓存被删除的时候会回调我们自定义的函数,并告知删除原因。


那么 Guava 是如何实现的呢?



根据 LocalCache 中的 getLiveValue() 中判断缓存过期时,跟着这里的调用关系就会一直跟到:



removeValueFromChain() 中的:



enqueueNotification() 方法会将回收的缓存(包含了 key,value)以及回收原因包装成之前定义的事件接口加入到一个本地队列中。



这样一看也没有回调我们初始化时候的事件啊。


不过用过队列的同学应该能猜出,既然这里写入队列,那就肯定就有消费。


我们回到获取缓存的地方:



在 finally 中执行了 postReadCleanup() 方法;其实在这里面就是对刚才的队列进行了消费:



一直跟进来就会发现这里消费了队列,将之前包装好的移除消息调用了我们自定义的事件,这样就完成了一次事件回调。


总结


以上所有源码:


github.com/crossoverJi…


通过分析 Guava 的源码可以让我们学习到顶级的设计及实现方式,甚至自己也能尝试编写。


Guava 里还有很多强大的增强实现,值得我们再好好研究。


相关文章
|
2月前
|
存储 缓存 Java
【Spring原理高级进阶】有Redis为啥不用?深入剖析 Spring Cache:缓存的工作原理、缓存注解的使用方法与最佳实践
【Spring原理高级进阶】有Redis为啥不用?深入剖析 Spring Cache:缓存的工作原理、缓存注解的使用方法与最佳实践
|
2月前
|
XML 存储 缓存
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache管理器的实战开发指南(修正篇)
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache管理器的实战开发指南(修正篇)
29 0
|
2月前
|
存储 XML 缓存
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南(一)
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南
78 0
|
5天前
|
消息中间件 存储 缓存
Guava的这些骚操作让我的代码量减少了50行
Guava的这些骚操作让我的代码量减少了50行
21 3
|
18天前
|
存储 缓存 Java
探秘MyBatis缓存原理:Cache接口与实现类源码分析
探秘MyBatis缓存原理:Cache接口与实现类源码分析
35 2
探秘MyBatis缓存原理:Cache接口与实现类源码分析
|
2月前
|
缓存 NoSQL Java
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南(二)
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南
34 0
|
11月前
|
设计模式 存储 开发框架
C++ 插件机制的实现原理、过程、及使用
C++ 插件机制的实现原理、过程、及使用
|
11月前
|
缓存 NoSQL Redis
【Redis原理机制 五】BloomFilter的实现原理及优化
【Redis原理机制 五】BloomFilter的实现原理及优化
71 0
|
11月前
【读代码】Guava的CacheLoader
【读代码】Guava的CacheLoader
68 0
|
12月前
|
存储 缓存 监控
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache抽象详解的核心原理探索
缓存的工作机制是先从缓存中读取数据,如果没有再从慢速设备上读取实际数据,并将数据存入缓存中。通常情况下,我们会将那些经常读取且不经常修改的数据或昂贵(CPU/IO)的且对于相同请求有相同计算结果的数据存储到缓存中。
149 1