网站首页 > 博客文章 正文
堆内缓存解决方案:Java堆内缓存与GuavaCache-问题描述
当数据库臃肿性能不佳时,需要通过多层缓存的方式,在不同层级上设置缓存,减少数据库的连接次数与查询次数。假设有这样一个场景,首先查询一次堆内缓存,如果没有命中堆内缓存,则需要在MySQL中进行查询,然后将查询结果放置在堆内缓存中,以免下次查询不到,最后返回数据。这种方案比较常见,但是会出现许多细节上的问题,例如:
(1)缓存穿透:DB中不存在数据,每次都穿过缓存查询DB,当给DB造成较大压力时应当如何处理?
(2)缓存击穿:在缓存失效的瞬间涌入大量请求,造成DB的压力瞬间增大,此时应当如何处理?
(3)缓存雪崩:大量缓存设置了相同的失效时间,使得性能瞬间急剧下降,此时应当如何处理?
(4)JDK中主要包含几种缓存形式?
问题分析与解决方案
针对在10.1节中提出的问题,均可使用Java堆内缓存和Guava Cache解决。
Java堆内缓存是Java在运行或初始化时创建的List、Map等全局变量容器。用户不仅可以用该容器存放数据,还可以从该容器中获取数据,而不需要从MySQL等数据库中获取,所以避免了缓存穿透等问题。
Guava Cache是Google公司开发的Java堆内缓存工具包(框架),它包含了若干被Google公司的Java项目广泛依赖的核心库。例如,集合、缓存、原生类型支持、并发库、通用注解、字符串处理和I/O等。所有这些工具每天都被Google公司的工程师应用在产品服务中。
Java堆内缓存--Java堆内缓存原理
在 JDK 中 , 堆 内 缓 存 容 器 有 HashMap 、 CopyOnWriteArrayList 和ConcurrentHashMap等,用户完全不必在意数据的分配、溢出和回收等操作,可全部交由JVM处理。由于JVM提供了诸多的垃圾回收算法,可以保证在不影响甚至微影响系统的前提下,做到对堆内缓存接近完美的管控。
堆内缓存与磁盘缓存相比减少了I/O操作,因此速度更快,效率更高。对于Redis等NoSQL缓存来说,堆内缓存减少了网卡流量的压力,不会因为网速的限制而出现性能的瓶颈。从本质上来看,堆内缓存是以牺牲内存为代价来减少I/O压力、网卡压力和CPU压力的。堆内缓存的速度最快,但也有很多缺点。例如,不能按照一定规则淘汰数据,缺少定制功能,缺少回调功能,有线程安全、容量溢出、垃圾回收发生卡顿等问题。
Java集合容器框架主要有四大类别:List、Set、Queue和Map,其分别包含线程安全与线程不安全两种实现方式。
在多线程高并发下,线程安全的容器通常使用快速失败模式进行构造,而线程不安全的容器通常使用安全失败模式进行构造。线程安全的容器通常在容器中使用synchronized关键字加锁,而线程不安全容器通常没有使用synchronized关键字加锁,所以从加锁角度来看,线程不安全容器的性能要高于线程安全容器的性能。
堆内缓存通常使用多线程高并发的Java集合容器框架,以免造成数据损坏或丢失。Guava Cache同样是多线程高并发的Java集合容器框架,但是与java.util.concurrent并发包下的容器相比,实现的功能更多,性能更好,使用起来也更轻松。
需要注意的是,堆内缓存占用大小要低于JVM大小,并且当堆内缓存使用过量时可能会出现频繁垃圾回收的情况,尤其注意不要出现Full GC和Dumping heap的情况,否则用堆内缓存提升性能的举动将会得不偿失,反而会提高系统压力,降低系统性能。
如果担心JVM存储的情况,则可以在本地更改JVM启动命令,将配置调低,测试相应情况是否会出现。或者模拟生产环境,观察是否能够承受相应的缓存大小。
假设使用Map存储key为String,value为JSON,并且单个JSON大小为1.3KB的数据,共存储一百万条,则仅此Map就需要约1GB的JVM。以此数值对比JVM的-Xmx最大堆大小,方可知道当前JVM最大堆内存是否能够支撑所有的缓存。通过Linux系统的top命令进行查看后进行调试,争取把JVM最大堆大小设置到合理数值,并且最大堆大小足以承受当前堆内缓存的容量。
Java堆内缓存中的常见算法及实战
Java堆内缓存中的常见算法如下。
1. 快速失败
快速失败(fail-fast)是Java编程中的常见概念,它在java.util包中被频繁使用。无论迭代器由何种方式创建,除非迭代器自身进行增删等相关操 作 , 否 则 在 修 改 了 容 器 中 的 数 据 之 后 , 迭 代 器 都 会 抛 出ConcurrentModificationException异常,进行彻底且迅速的失败,而不是贸然将数据返回。因此如果是并发修改数据,则容器将直接抛出异常。
通常来说,迭代器的快速失败行为是无法被保证的,所以不能依赖此特性编写程序。面对迭代器的快速失败,正确做法是检测bug及相关错误。由此可见,当用迭代器遍历一个集合对象时,如果在遍历过程中对集合对象的内容 进 行 了 修 改 ( 增 加 、 删 除 、 修 改 ) , 则 会 抛 出ConcurrentModificationException异常。
快速失败的原理是,迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用了一个modCount变量。集合在被遍历期间,如果它的内容发生了变化,就会改变modCount变量的值。迭代器在使用hashNext()/next遍历下一个元素之前,会先检测modCount变量是否为预期值,是就返回遍历;否则抛出异常,终止遍历。
java.util包下的容器都为快速失败类型的容器。Java快速失败的代码如下所示:
执行结果如下所示:
生成这个结果的原因是,java.util.ArrayList的迭代器Iterator的next函数抛出了ConcurrentModificationException异常,其next函数如下所示:
2. 安全失败
采用安全失败(fail-safe)机制的集合容器,在遍历时并不直接访问集合内容,而是先复制原有集合内容,再在拷贝的集合上进行遍历,因此不会触发ConcurrentModificationException异常。
java.util.concurrent并发包下的容器都是安全失败类型的容器,可以在多线程下并发使用、并发修改。java.util.concurrent并发包下的容器都被称之为并发容器,而HashMap和ArrayList不能作为并发容器使用。在java.util.concurrent 并 发 包 下 , 主 要 包 含 ConcorrenctHashMap 、CopyOnWriteArrayList 、 ReentrantLock 、 ReentrantReadWriteLock 、CyclicBarrier、CountDownLatch、Semaphore和Exchanger等相关容器。
安全失败与快速失败相比,更加消耗内存,但是性能也更好,是一种以空 间 换 时 间 的 做 法 。 将 快 速 失 败 案 例 中 的 ArrayList 实 现 换 成CopyOnWriteArrayList实现,即可体验Java安全失败,代码如下所示:
执行结果如下所示:
生成这个结果的原因是,java.util.concurrent.CopyOnWriteArrayList的迭代器Iterator的next函数没有抛出ConcurrentModificationException异常,其next函数如下所示:
3. 无锁算法
CAS算法是一个有名的无锁算法,它是区别于同步锁(Synchronized)的一种乐观锁。当多个线程同时尝试使用CAS算法并更新同一个变量时,只有一个线程能更新变量的值。失败的线程并不会被挂起,而是被告知在这次竞争中失败,可以再次尝试。当然也允许失败的线程放弃操作。基于这样的原理,即使没有锁,CAS算法也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
CAS算法对死锁问题天生免疫,并且线程间的相互影响远远小于基于锁的方式。更为重要的是,使用无锁的方式不仅完全没有锁竞争带来的系统开销,而且没有线程间频繁调度带来的开销,因此,它的性能更优越。
CAS算法中通常包含三个参数:
(1)要更新的变量(旧值)。
(2)预期的旧值。
(3)新值。
仅当“要更新的变量(旧值)”与“预期的旧值”相同时,才会将“新值”设为“要更新的变量(旧值)”。如果“要更新的变量(旧值)”和“预期的旧值”不同,则说明已经有其他线程做了更新,因而当前线程什么都不做。最后,CAS算法返回当前“要更新的变量(旧值)”。
用Java实现CAS算法的代码如下所示:
即使在代码中并没有使用synchronized之类的关键字和锁,仍然可以保证数据的安全性,这便是CAS算法的初衷。在上述代码中如果“要更新的变量(旧值)”与“预期的旧值”不符,则无法赋予“新值”。
上述代码为了显示直观,将值的类型设置为int。此处用任何类型皆可,包括String、Object、Map、Set和List等,也可以多写一些功能性函数。注意,当值的类型不是int时,要增加各种非空验证,并及时抛出异常。
4. 分段锁算法
下面通过Map中的Key-Value结构简易理解分段锁(Segment)算法。它并是不给整体的Map进行上锁,而是给Map中单独的key值进行上锁。当请求该key值时,需要先请求锁,这样即可保证Map响应的性能和效率。当不是所有请求都竞争Map内的一个锁时,可避免对整个Map的锁定。可能出现几十个线程请求同一个Map的情况,但是请求的是该Map内的十几个锁。
用Java实现分段锁算法的代码如下所示:
上述代码中的Thread.sleep可用来测试分段锁是否真实生效,测试代码如下所示:
上述代码中的main方法同时启动3个线程:
(1)将Map中key值为1的value设置为2。
(2)获取Map中key值为1的value。
(3)获取Map中key值为2的value。
上面的代码为了方便高并发测试,同时启动多个线程,因此可以直观地看到最终结果,如下所示:
输出:null
即在put操作key值为1的过程中,同时取key值为1的value是无法取到数据 的 , 但 是 可 以 对 Map 中 key 值 为 2 的 value 进 行 操 作 。 如 果 删 除Thread.sleep();,输出结果将如下所示:
输出:2
输出:null
5. 跳表算法
跳表(Skip List)是一个随机化的数据结构,可以被看作二叉树的一个变种。它在性能上和红黑树、AVL树不相上下,但是原理非常简单,目前在Redis和LevelDB中都有用到。
即使对于排过序的链表,在查找或插入等操作时还是需要对链表进行遍历,不仅查询时间很长,而且需要查询的内容也很多。跳表算法属于结合二分查找思想与有序链表结构之后衍生出的算法。跳表算法的原理是,对于一个有序链表,选取它中间的节点来构建索引。由于链表经过排序处理,所以如果需要插入的值大于索引,则只需查询索引之前的内容即可完成遍历。假如原始链表长度为M,可以在链表内增加N个索引,跳表通过优先遍历N个索引,与所需要插入的元素进行比较,在得到距离最近的A号与A+1号索引后,遍历A号与A+1号索引中间的所有数据,即可找到待插入元素的位置。
跳表是一种以空间换时间的算法,虽然存储空间会增大,但是会极大地减少查询所需的时间,提高查询效率。原始链表越长,跳表算法所体现出来的价值越明显。跳表算法的实战代码如下所示:
结果如下所示:
6. 从0到1编写ArrayList
下 面 我 们 通 过 快 速 失 败 或 安 全 失 败 的 方 式 构 造 ArrayList 容 器 或CopyOnWriteArrayList容器的基础增删改查功能。
在构造基础容器及其增删改查算法之后,可通过无锁算法、分段锁算法、跳表算法优化容器增删改查的执行速度与增删改时的数据一致性强度。
下面从0到1编写ArrayList,代码如下所示:
自定义的容器为快速失败类型的容器。快速失败的测试代码与展示结果皆与前文相同,代码如下所示:
结果如下所示。
Guava Cache实战
Guava Cache的主要功能有避免NULL、堆内缓存条件性查询、区间查询、优化Object类的函数、排序、简化异常、自定义创建集合等。
由于Guava Cache包含了Google中的集合、缓存和并发库等内容,所以在项目中只需引入Guava Cache即可,其Maven坐标如下所示:
在Google容器(com.google.common.collect包)中实现了部分JDK容器,并且Google中的容器可以与JDK中的容器一一对应,例如:
? Google中的Collections2容器对应JDK中的Collection容器。
? Google中的Lists容器对应JDK中的List容器。
? Google中的Sets容器对应JDK中的Set容器和SortedSet容器。
? Google中的Maps容器对应JDK中的Map容器和SortedMap容器。
? Google中的Queues容器对应JDK中的Queue容器。
创建Google的容器工厂
Google容器的设计模式是以静态工厂的模式进行构造的,其初始构造代码如下所示:
Google容器可以初始化存入一些元素,其初始化构造代码如下所示:
newArrayList函数的本质是创建一个ArrayList函数,并向其中赋值,其底层关键性代码如下所示:
屏蔽NULL值
如果Java语法中的Map、Set容器的key值为NULL,则有可能出现空指针异常现象。许多缓存工具包(框架)对NULL值都采用快速失败的方式进行处理,另外,部分缓存工具本身也提供了对NULL值的特殊处理方式,例如,赋予一个默认值。
Guava Cache包含以上两种处理方式,它提供了许多工具类,方便用户将特定值替换成NULL值,避免空指针异常现象的发生。
使用Guava Cache的核心代码如下所示:
除屏蔽NULL值外,Guava Cache还可以判断许多检查条件函数,部分函数如下所示:
? checkArgument(boolean):检查boolean是否为true,用来检查传递给方法的参数。当检查失败时会抛出IllegalArgumentException异常。
? checkNotNull(T):检查value是否为null。因为该方法直接返回value , 所 以 可 以 内 嵌 checkNotNull 。 当 检 查 失 败 时 会 抛 出NullPointerException异常。
? checkState(boolean):用来检查对象的某些状态。当检查失败时会抛出IllegalStateException异常。
? checkElementIndex(int index, int size):检查当index作为索引值时对某个列表、字符串或数组是否有效。例如,index>=0 && index<size*。当检查失败时会抛出IndexOutOfBoundsException异常。
? checkPositionIndex(int index, int size):检查当index作为位置值时对某个列表、字符串或数组是否有效。例如,index>=0 && index<=size*。
当检查失败时会抛出IndexOutOfBoundsException异常。
? checkPositionIndexes(int start, int end, int size) : 检 查[start, end]表示的位置范围对某个列表、字符串或数组是否有效。当检查失败时会抛出IndexOutOfBoundsException异常。
管理字符串
Google容器的Joiner字符串连接器是由Fluent风格代码编写的,可以方便地用分隔符把字符串序列连接起来,并且自动删除空值等,其部分代码如下所示:
上 面 的 代 码 将 返 回 “Harry; Ron; Hermione” 。 另 外 ,useForNull(String) 方 法 可 以 给 定 某 个 字 符 串 来 替 换 NULL , 而 不 像skipNulls()方法那样直接忽略NULL。
除Google容器的Joiner字符串连接器外,Google还包含拆分器、字符匹配器、字符集管理器和大小格式管理器等,方便对字符串进行各种处理,如分割、连接或填充等,其详细内容可查询Google的GitHub用户指南。
操作Google的Multiset容器
Multiset容器定义了如何多次添加相等的元素,并且可以记录多次添加相 等 元 素 的 次 数 , 其 包 含 HashMultiset 、 TreeMultiset 、LinkedHashMultiset、ConcurrentHashMultiset和ImmutableMultiset等多种实现方式。部分API如下所示:
? add(E element):向其中添加单个元素。
? add(E element,int occurrences):向其中添加指定个数的元素。
? count(Object element):返回给定参数元素的个数。
? remove(E element):移除一个元素,其count值会相应减少。
? remove(E element,int occurrences):移除相应个数的元素。
? elementSet():将不同的元素放入一个Set中。
? entrySet():类似于Map.entrySet返回Set<Multiset.Entry>。包含的Entry支持使用getElement()和getCount()。
? setCount(E element ,int count):设定某个元素的重复次数。
? setCount(E element,int oldCount,int newCount):将符合原有重复次数的元素修改为新的重复次数
? retainAll(Collection c):保留出现在给定集合参数中的所有元素。
? removeAll(Collectionc):去除出现在给定集合参数中的所有元素。
Multiset容器的示例代码如下所示:
操作Google的Multimap容器
Multimap 容 器 定 义 了 如 何 把 键 映 射 到 多 个 元 素 上 , 其 包 含ArrayListMultimap、HashMultimap、LinkedListMultimap、LinkedHashMultimap、TreeMultimap、ImmutableListMultimap、ImmutableSetMultimap和Multimap等多种实现方式,部分API如下所示:
? put(K, V):添加键到单个值的映射。
? putAll(K, Iterable<V>):依次添加键到多个值的映射。
? remove(K, V):移除键到值的映射。如果有这样的键值并成功移除,则返回true。
? removeAll(K):删除键对应的所有值,返回的集合包含所有之前映射到K的值。注意,修改这个集合不会影响Multimap。
replaceValues(K, Iterable<V>):删除键对应的所有值,并重新把Key关联到Iterable中的每个元素。返回的集合包含所有之前映射到K的值。
? asMap():Multimap<K, V>提供Map<K,Collection<V>>形式的视图。返回的Map支持remove操作,并且会反映底层的Multimap容器,但它不支持put或putAll操作。更重要的是,如果想为Multimap容器中没有的键返回NULL,而不是一个新的、可写的空集合,则可以使用asMap().get(key)。我们应当把 asMap.get(key) 返 回 的 结 果 转 化 为 适 当 的 集 合 类 型 , 例 如 , 把SetMultimap.asMap.get(key)的结果转换为Set,把ListMultimap.asMap.get(key) 的 结 果 转 换 为 List 。 在 Java 中 , 不 允 许ListMultimap直接为asMap.get(key)返回List,当然,可以用Multimap容器中的asMap静态方法完成类型转换。
Multimap容器的示例代码如下所示:
操作Google的BiMap容器
BiMap容器定义了如何同时维护并同步两个单独的map,即通过value值可以寻找到其key值。BiMap包含HashBiMap、EnumBiMap和ImmutableBiMap等多种实现方式,部分API如下所示:
? put(K key, V value):关联指定值与此映射中(可选操作)指定的键。
? forcePut(K key, V value):默默删除在put(K, V)运行前的所有条目值。
? inverse():返回此BiMap容器,把每一个BiMap容器的值都映射到其相关联的键的逆视图中。
? putAll(Map<? extends K,? extends V> map):指一次性向一个哈希键添加多个Key映射。
? Set<V> values():返回此映射中包含Collection的值视图。
BiMap容器的示例代码如下所示:
操作Google的Table容器
Table容器定义了以两个key值,它们以坐标系的形式指向一个元素。
Table 容 器 包 含 HashBasedTable 、 TreeBasedTable 、 ImmutableTable 和ArrayTable等多种实现方式,部分API如下所示:
? cellSet():返回集合中的所有行键/列键/值三元组。
? column(C columnKey):返回给定列键的所有映射的视图。
? columnKeySet():返回一组具有表中一个或多个值的列键。
? columnMap():返回关联的每一个列键与行键对应的映射值的视图。
? contains(Object rowKey, Object columnKey):返回表中是否包含指定的行键和列键的映射。
? containsColumn(Object columnKey):返回表中是否包含指定列键的映射。
? containsRow(Object rowKey):返回表中是否包含指定行键的映射。
? containsValue(Object value):返回表中是否包含指定列值的映射。
? get(Object rowKey, Object columnKey):对于给定的行键和列键,返回表中是否存在相应的映射。如果不存在,则返回NULL。
? put(R rowKey, C columnKey, V value):放置值。
? move(Object rowKey, Object columnKey):删除值。
Table容器的示例代码如下所示:
结果如下所示:
操作Google的classToInstanceMap容器
classToInstanceMap容器定义了键为类型而值符合键所指类型的对象。
classToInstanceMap容器包含MutableClassToInstanceMap和ImmutableClassToInstanceMap等多种实现方式,部分API如下所示:
? getInstance(Class<T> type):返回指定类映射到的值。如果不存在该类的条目,则返回NULL。
? putInstance(Class<T> type, T value):将指定的类映射到指定的值。
classToInstanceMap容器的示例代码如下所示:
操作Google的RangeSet容器
RangeSet容器定义了一组不相连的、非空区间。当把一个区间添加到可变的RangeSet容器时,所有相连的区间会被合并,空区间会被忽略。
RangeSet容器包含ImmutableRangeSet和TreeRangeSet等多种实现方式,部分API如下所示:
? complement() : 返 回 RangeSet 容 器 的 补 集 视 图 。 complement 是RangeSet容器中的类型,它包含了不相连的、非空区间。
? subRangeSet(Range<C>):返回RangeSet容器与给定Range对象的交集视图。这扩展了传统排序集合中的headSet、subSet和tailSet操作。
? asRanges():用Set<Range<C>>表现RangeSet容器,这样可以遍历其中的Range对象。
? asSet(DiscreteDomain<C>) ( 仅 ImmutableRangeSet 支 持 ) : 用ImmutableSortedSet<C>表现RangeSet容器,以区间中所有元素的形式而不是区间本身的形式查看。该操作不支持DiscreteDomain和RangeSet容器都没有上边界,或者都没有下边界的情况。
? contains(C):RangeSet容器中最基本的操作,判断在RangeSet容器中是否有任何区间包含给定元素。
? rangeContaining(C):返回包含给定元素的区间。若没有这样的区间,则返回NULL。
? encloses(Range<C>):判断RangeSet容器中是否有区间,包括给定区间。
? span():返回RangeSet容器中所有区间的最小区间。
在RangeSet容器链表区间内包含Range对象,即范围对象,在对象内部可以存储范围值。对象之间可以进行交集、并集或跨区间等相关运算。Range对象的部分API如下所示:
? Range.open(C lower, C upper):存储为a < x < b。
? Range.closed(C lower, C upper):存储为a ≤= x ≤ b。
? Range.openClosed(C lower, C upper):存储为a < x≤ b。
? Range.closedOpen(C lower, C upper):存储为a≤ x < b。
? Range.greaterThan(C endpoint):存储为x > a。
? Range.atLeast(C endpoint):存储为x ≤a。
? Range.lessThan(C endpoint):存储为x < b。
? Range.atMost(C endpoint):存储为x ≤ b。
? Range.all(C endpoint):存储为x。
其中x为存储之后的范围,a为输入的第一个值,b为输入的第二个值。
Range对象的示例代码如下所示:
RangeSet容器的示例代码如下所示:
操作Google的RangeMap容器
RangeMap容器类似于RangeSet容器,只是存储的范围值为Map,示例代码如下所示:
操作Google的Guava Cache
Guava Cache与ConcurrentMap容器十分相似,它们之间最根本的区别是ConcurrentMap容器会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache为了限制内存占用,通常设定为自动回收元素。在某些场景下,尽管LoadingCache不回收元素,但是它会自动加载缓存。
(1)Guava Cache的适用场景如下:
? 愿意消耗一些内存空间来提升速度。
? 预料到某些键会被查询一次以上。
? 缓存中存放的数据总量不超出内存容量,即不会超出JVM的总内存。
(2)构建Guava Cache。构建Guava Cache的代码十分简单,基础构建代码如下所示:
此时既可以使用.asMap()函数把Cache对象转换成类似于Map的对象,对其进行增删改查,也可以使用各种Guava Cache构建参数进行构建。参数化构建Guava Cache的代码如下所示:
上面代码的释义如下所示:
? expireAfterWrite(3, TimeUnit.SECONDS):若缓存项在给定时间内没有被写访问(创建或覆盖),则回收。
? expireAfterAccess(3, TimeUnit.SECONDS):若缓存项在给定时间内没有被读/写访问,则回收。
? weakValues():使用弱引用存储值。当值没有其他(强或软)引用时,缓存项可以被垃圾回收。默认为强引用。
? weakKeys():使用弱引用存储键。当键没有其他(强或软)引用时,缓存项可以被垃圾回收。默认为强引用。
? softValues():使用软引用存储值。只有在响应内存需要时,软引用才按照全局最近最少使用的顺序进行回收。另外,soft Values()与weakValues()只能使用其中一个。
? concurrencyLevel(8):设置并发级别为8。并发级别指可以同时写缓存的线程数。
? initialCapacity(10):设置缓存容器的初始容量为10。
? maximumSize(2000):在最大存储上限超过2000之后,按照LRU算法移除缓存项。
? recordStats():设置要统计缓存的命中率。
? removalListener(new RemovalListener()):设置缓存的移除监听(通知)。
? build:构建Guava缓存,其内部的new CacheLoader()可不填。newCacheLoader()的含义为当缓存不存在时,可以通过CacheLoader的实现自动加载缓存。
(3)Guava Cache的视图。为了方便操作,Guava Cache提供了Map视图的方式进行操作,代码如下所示:
输出如下所示:
值得注意的是,虽然生成了asMap视图,并且只在视图内进行增删改查,但是实际上同样会影响Guava Cache内的数据。
? cache.asMap() 包 含 当 前 所 有 加 载 到 缓 存 的 项 。 相 应 地 ,cache.asMap().keySet()包含当前所有已加载键。
? asMap().get(key)实际上等同于cache.getIfPresent(key),而且不会引起缓存项的加载。
所 有 读 和 写 操 作 都 会 重 置 相 关 缓 存 项 的 访 问 时 间 , 包 括cache.asMap().get(Object)方法和Cache.asMap().put(K, V)方法,但不包括Cache.asMap().containsKey(Object)方法,以及在Cache.asMap()的集合视图上的操作。例如,遍历Cache.asMap().entrySet()不会重置缓存项的读取时间。
(4)Guava Cache的垃圾回收。可以对写入时间、读写访问时间、存储上线大小等进行垃圾回收,除此之外,还可以通过手动的方式进行垃圾回收,如下所示:
? 从视图中删除:Cache.asMap.remove(key)。
? 个别删除:Cache.invalidate(key)。
? 批量删除:Cache.invalidateAll(keys)。
? 删除所有缓存项:Cache.invalidateAll()。
(5)Guava Cache的Callable回调。如果Guava Cache在读取时无法读取到其key值的相应缓存,可以使用其他函数,代码如下所示:
用Lambda表达式书写,代码如下所示:
(6)Guava Cache的监听。Guava Cache在移除数据时,有时需要一个回调,即通知程序做一些额外的操作。Guava Cache的监听代码如下所示:
监听代码需要实现RemovalListener接口。RemovalListener接口是接收通知的对象。
getCause()函数是获得触发条件的原因,其包含内容如下:
? EXPLICIT:用户已手动删除该数据,这可能是由于用户调用所导致的。
? REPLACED:该数据实际上没有被删除,但其值已被用户替换,这可能是由于用户调用所导致的。
? COLLECTED:该数据已自动删除,因为其键或值已被垃圾回收,在使用时可能会发生这种情况。
? EXPIRED:该数据的时间戳已过期,在使用时可能发生这种情况。
? SIZE:由于大小限制,该数据被逐出,在使用时可能发生这种情况。
在创建cache时,只能添加1个监听器,该监听器对象会被多个线程共享。如果监听器需要操作共享资源,那么一定要做好同步控制。如果强行添加了两个监听器,则两个监听器会交替执行任务。
(7)Guava Cache的统计。CacheBuilder.recordStats()可用来开启Guava Cache的统计功能。在统计功能打开后,Cache.stats()方法会返回CacheStats对象,并提供如下统计信息:
? stats.hitRate():缓存命中率。
? stats.averageLoadPenalty():加载新值的平均时间,单位为纳秒。
? stats.evictionCount():缓存项被回收的总数,不包括显式删除。
本文给大家讲解的内容是堆内缓存解决方案:Java堆内缓存与GuavaCache
- 下文给大家讲解的是堆外缓存与磁盘缓存解决方案:MapDB
猜你喜欢
- 2025-01-01 第一篇|Spark概览
- 2025-01-01 蔚来真题和答案,主打一个简单?
- 2025-01-01 ABB工业机器人编程指令函数程序数据等
- 2025-01-01 eNodeB启动和RRC连接建立
- 2025-01-01 硬盘SMART检测参数详解
- 2025-01-01 富集分析,看完这篇就够
- 2025-01-01 18个并发场景的设计模式详解,有没有你的盲区
- 2025-01-01 并发编程常见问题
- 2025-01-01 阿里架构师亲自梳理的:多线程与高并发知识点
- 2025-01-01 应读者要求讲讲 DMA
你 发表评论:
欢迎- 368℃用AI Agent治理微服务的复杂性问题|QCon
- 362℃手把手教程「JavaWeb」优雅的SpringMvc+Mybatis整合之路
- 358℃初次使用IntelliJ IDEA新建Maven项目
- 351℃Maven技术方案最全手册(mavena)
- 348℃安利Touch Bar 专属应用,让闲置的Touch Bar活跃起来!
- 347℃InfoQ 2024 年趋势报告:架构篇(infoq+2024+年趋势报告:架构篇分析)
- 345℃IntelliJ IDEA 2018版本和2022版本创建 Maven 项目对比
- 343℃从头搭建 IntelliJ IDEA 环境(intellij idea建包)
- 最近发表
- 标签列表
-
- powershellfor (55)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)