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 里还有很多强大的增强实现,值得我们再好好研究。


相关文章
|
缓存 NoSQL Java
Java工具篇之Guava-cache内存缓存
常在业务系统中做开发,不会点高级知识点,有点不好意思了。在业务系统中,提高系统响应速度,提供系统高并发能力,其实方向很简单,三个方向,六个字而已: **缓存降级限流。** 当然这是在排除代码质量非常差的情况,如果代码质量很差,都是while循环和高内存占用,那么其实再怎么做都于事无补。除非你有一个马云爸爸,性能不够,机器来凑嘛。阿里云前来支持(1000台机器够了吗?)
1287 0
|
7月前
|
XML 存储 缓存
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache管理器的实战开发指南(修正篇)
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache管理器的实战开发指南(修正篇)
120 0
|
4月前
|
缓存 Java Spring
Java本地高性能缓存实践问题之Caffeine中设置刷新机制的问题如何解决
Java本地高性能缓存实践问题之Caffeine中设置刷新机制的问题如何解决
128 1
|
4月前
|
缓存 Java
Java本地高性能缓存实践问题之使用Caffeine的Cache接口来查找一个缓存元素的问题如何解决
Java本地高性能缓存实践问题之使用Caffeine的Cache接口来查找一个缓存元素的问题如何解决
|
7月前
|
存储 XML 缓存
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南(一)
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南
479 0
|
4月前
|
存储 缓存 监控
Java本地高性能缓存实践问题之Guava Cache被Caffeine所取代的问题如何解决
Java本地高性能缓存实践问题之Guava Cache被Caffeine所取代的问题如何解决
|
4月前
|
缓存 Java Spring
Java本地高性能缓存实践问题之的Caffeine设置刷新机制问题如何解决
Java本地高性能缓存实践问题之的Caffeine设置刷新机制问题如何解决
128 0
|
存储 设计模式 缓存
Java源码分析:Guava之不可变集合ImmutableMap的源码分析
Java源码分析:Guava之不可变集合ImmutableMap的源码分析
66 0
|
7月前
|
缓存 NoSQL Java
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南(二)
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南
161 0
|
存储 缓存 监控
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache抽象详解的核心原理探索
缓存的工作机制是先从缓存中读取数据,如果没有再从慢速设备上读取实际数据,并将数据存入缓存中。通常情况下,我们会将那些经常读取且不经常修改的数据或昂贵(CPU/IO)的且对于相同请求有相同计算结果的数据存储到缓存中。
197 1