一、概述

1、内存缓存

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

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

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

2、核心数据结构

和jdk7的HashMap相似

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

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

二、具体实现

0、一览众山小

主要的类:

CacheBuilder 设置参数,构建Cache

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

1、CacheBuilder 构建器

提要:

记录所需参数

  1. public final class CacheBuilder<K, V> {
  2. public <K1 extends K, V1 extends V> LoadingCache<K1, V1> build(
  3. CacheLoader<? super K1, V1> loader) { // loader是用来自动刷新的
  4. checkWeightWithWeigher();
  5. return new LocalCache.LocalLoadingCache<>(this, loader);
  6. }
  7. public <K1 extends K, V1 extends V> Cache<K1, V1> build() { // 这个没有loader,就不会自动刷新
  8. checkWeightWithWeigher();
  9. checkNonLoadingCache();
  10. return new LocalCache.LocalManualCache<>(this);
  11. }
  12. int initialCapacity = UNSET_INT; // 初始map大小
  13. int concurrencyLevel = UNSET_INT; // 并发度
  14. long maximumSize = UNSET_INT;
  15. long maximumWeight = UNSET_INT;
  16. Weigher<? super K, ? super V> weigher;
  17. Strength keyStrength; // key强、弱、软引,默认为强
  18. Strength valueStrength; // value强、弱、软引,默认为强
  19. long expireAfterWriteNanos = UNSET_INT; // 写过期
  20. long expireAfterAccessNanos = UNSET_INT; //
  21. long refreshNanos = UNSET_INT; //
  22. Equivalence<Object> keyEquivalence; // 强引时为equals,否则为==
  23. Equivalence<Object> valueEquivalence; // 强引时为equals,否则为==
  24. RemovalListener<? super K, ? super V> removalListener; // 删除时的监听
  25. Ticker ticker; // 时间钟,用来获得当前时间的
  26. Supplier<? extends StatsCounter> statsCounterSupplier = NULL_STATS_COUNTER; // 计数器,用来记录get或者miss之类的数据
  27. }

2、LocalCache

1)初始化

提要:

a)赋值

b)初始化Segment[]数组

  1. LocalCache(
  2. CacheBuilder<? super K, ? super V> builder, @Nullable CacheLoader<? super K, V> loader) {
  3. // a)把builder的参数赋值过来,略
  4. // b)构建Segment[]数组,原理可参照jdk7点concurrentHashMap
  5. int segmentShift = 0;
  6. int segmentCount = 1; // 设置为刚刚好比concurrencyLevel大的2的幂次方的值
  7. while (segmentCount < concurrencyLevel && (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
  8. ++segmentShift;
  9. segmentCount <<= 1;
  10. }
  11. this.segmentShift = 32 - segmentShift;
  12. segmentMask = segmentCount - 1;
  13. this.segments = newSegmentArray(segmentCount);
  14. int segmentCapacity = initialCapacity / segmentCount; //每个Segment的容量
  15. int segmentSize = 1; // 刚刚好比容量大的2等幂次方的值
  16. while (segmentSize < segmentCapacity) {
  17. segmentSize <<= 1;
  18. }
  19. if (evictsBySize()) {
  20. // Ensure sum of segment max weights = overall max weights
  21. long maxSegmentWeight = maxWeight / segmentCount + 1;
  22. long remainder = maxWeight % segmentCount;
  23. for (int i = 0; i < this.segments.length; ++i) {
  24. if (i == remainder) {
  25. maxSegmentWeight--;
  26. }
  27. this.segments[i] =
  28. createSegment(segmentSize, maxSegmentWeight, builder.getStatsCounterSupplier().get());
  29. }
  30. } else {
  31. for (int i = 0; i < this.segments.length; ++i) {
  32. this.segments[i] =
  33. createSegment(segmentSize, UNSET_INT, builder.getStatsCounterSupplier().get()); // 往Segment数组里塞
  34. }
  35. }
  36. }
  37. Segment(
  38. LocalCache<K, V> map,
  39. int initialCapacity,
  40. long maxSegmentWeight,
  41. StatsCounter statsCounter) {
  42. this.map = map;
  43. this.maxSegmentWeight = maxSegmentWeight;
  44. this.statsCounter = checkNotNull(statsCounter);
  45. initTable(newEntryArray(initialCapacity));
  46. // 当key是弱、软引用时,初始化keyReferenceQueue;其父类特性决定其gc时,会将被GC的元素放入该队列中
  47. keyReferenceQueue = map.usesKeyReferences() ? new ReferenceQueue<K>() : null;
  48. valueReferenceQueue = map.usesValueReferences() ? new ReferenceQueue<V>() : null;
  49. recencyQueue =
  50. map.usesAccessQueue()
  51. ? new ConcurrentLinkedQueue<ReferenceEntry<K, V>>()
  52. : LocalCache.<ReferenceEntry<K, V>>discardingQueue();
  53. writeQueue =
  54. map.usesWriteQueue()
  55. ? new WriteQueue<K, V>()
  56. : LocalCache.<ReferenceEntry<K, V>>discardingQueue();
  57. accessQueue =
  58. map.usesAccessQueue()
  59. ? new AccessQueue<K, V>()
  60. : LocalCache.<ReferenceEntry<K, V>>discardingQueue();
  61. }

2)put

提要

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

b)锁住segment,清理

i)如果key存在

ii)如果key不存在

c)清理

  1. class LocalCache {
  2. public V put(K key, V value) {
  3. checkNotNull(key);
  4. checkNotNull(value);
  5. int hash = hash(key); // 计算hash
  6. return segmentFor(hash).put(key, hash, value, false); // 找到hash所分配到的的Segment,put进去
  7. }
  8. }
  9. // 转而来看Segment的put方法
  10. class Segment<K,V> implements ReentrantLock {
  11. V put(K key, int hash, V value, boolean onlyIfAbsent) {
  12. lock(); // 锁住一个segment
  13. try {
  14. long now = map.ticker.read(); //获得当前时间
  15. preWriteCleanup(now); //清除软/弱引用 详见 2.4
  16. int newCount = this.count + 1;
  17. if (newCount > this.threshold) { // 如有需要则扩容
  18. expand();
  19. newCount = this.count + 1;
  20. }
  21. AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
  22. int index = hash & (table.length() - 1);
  23. ReferenceEntry<K, V> first = table.get(index);
  24. // Look for an existing entry.
  25. // 根据不同情况决定是否要执行操作,1)count++ 更新数量 2)enqueueNotification 入队通知 3)setValue 更新值 4)evictEntries 淘汰缓存
  26. for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
  27. K entryKey = e.getKey();
  28. // 如果该key已经存在
  29. if (e.getHash() == hash
  30. && entryKey != null
  31. && map.keyEquivalence.equivalent(key, entryKey)) {
  32. // We found an existing entry.
  33. ValueReference<K, V> valueReference = e.getValueReference();
  34. V entryValue = valueReference.get();
  35. if (entryValue == null) {
  36. ++modCount;
  37. if (valueReference.isActive()) {
  38. enqueueNotification(
  39. key, hash, entryValue, valueReference.getWeight(), RemovalCause.COLLECTED);
  40. setValue(e, key, value, now);
  41. newCount = this.count; // count remains unchanged
  42. } else {
  43. setValue(e, key, value, now);
  44. newCount = this.count + 1;
  45. }
  46. this.count = newCount; // write-volatile
  47. evictEntries(e);
  48. return null;
  49. } else if (onlyIfAbsent) {
  50. recordLockedRead(e, now);
  51. return entryValue;
  52. } else {
  53. // clobber existing entry, count remains unchanged
  54. ++modCount;
  55. enqueueNotification(
  56. key, hash, entryValue, valueReference.getWeight(), RemovalCause.REPLACED);
  57. setValue(e, key, value, now);
  58. evictEntries(e);
  59. return entryValue;
  60. }
  61. }
  62. }
  63. // 如果该key不存在,则新建一个entry.
  64. ++modCount;
  65. ReferenceEntry<K, V> newEntry = newEntry(key, hash, first);
  66. setValue(newEntry, key, value, now);
  67. table.set(index, newEntry);
  68. newCount = this.count + 1;
  69. this.count = newCount; // write-volatile
  70. evictEntries(newEntry);
  71. return null;
  72. } finally {
  73. unlock();
  74. postWriteCleanup();
  75. }
  76. }
  77. @GuardedBy("this")
  78. ReferenceEntry<K, V> newEntry(K key, int hash, @Nullable ReferenceEntry<K, V> next) {
  79. return map.entryFactory.newEntry(this, checkNotNull(key), hash, next);
  80. }
  81. }

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

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

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

  1. enum EntryFactory {
  2. STRONG {
  3. @Override
  4. <K, V> ReferenceEntry<K, V> newEntry(
  5. Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
  6. return new StrongEntry<>(key, hash, next);
  7. }
  8. },...,// 省略部分
  9. WEAK { // 软/弱引用的精髓!!!
  10. @Override
  11. <K, V> ReferenceEntry<K, V> newEntry(
  12. Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next) { // 所以!!!就是在这里!!!把这个queue放进去了,终于找到了
  13. return new WeakEntry<>(segment.keyReferenceQueue, key, hash, next);
  14. }
  15. }};
  16. // Masks used to compute indices in the following table.
  17. static final int ACCESS_MASK = 1;
  18. static final int WRITE_MASK = 2;
  19. static final int WEAK_MASK = 4;
  20. /** Look-up table for factories. */
  21. static final EntryFactory[] factories = {
  22. STRONG,
  23. STRONG_ACCESS,
  24. STRONG_WRITE,
  25. STRONG_ACCESS_WRITE,
  26. WEAK,
  27. WEAK_ACCESS,
  28. WEAK_WRITE,
  29. WEAK_ACCESS_WRITE,
  30. };
  31. static EntryFactory getFactory(
  32. Strength keyStrength, boolean usesAccessQueue, boolean usesWriteQueue) {
  33. int flags =
  34. ((keyStrength == Strength.WEAK) ? WEAK_MASK : 0)
  35. | (usesAccessQueue ? ACCESS_MASK : 0)
  36. | (usesWriteQueue ? WRITE_MASK : 0);
  37. return factories[flags];
  38. }
  39. // 抽象方法:创建一个entry
  40. abstract <K, V> ReferenceEntry<K, V> newEntry(
  41. Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next);
  42. }
  43. static class WeakEntry<K, V> extends WeakReference<K> implements ReferenceEntry<K, V> {
  44. WeakEntry(ReferenceQueue<K> queue, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
  45. super(key, queue); // 抽丝剥茧,这个是Reference的方法,所以放到这个queue里面去,是Java WeakReference类自带的功能
  46. this.hash = hash;
  47. this.next = next;
  48. }
  49. }

3)get

提要

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

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

c)清理

  1. class LocalCache{
  2. public @Nullable V get(@Nullable Object key) {
  3. if (key == null) {
  4. return null;
  5. }
  6. int hash = hash(key);
  7. return segmentFor(hash).get(key, hash);
  8. }
  9. }
  10. class Segment{
  11. V get(Object key, int hash) {
  12. try {
  13. if (count != 0) { // read-volatile
  14. long now = map.ticker.read();
  15. ReferenceEntry<K, V> e = getLiveEntry(key, hash, now); //如果发现没有找到或者过期了,则返回为null
  16. if (e == null) {
  17. return null;
  18. }
  19. V value = e.getValueReference().get();
  20. if (value != null) {
  21. recordRead(e, now);
  22. return scheduleRefresh(e, e.getKey(), hash, value, now, map.defaultLoader);// 如果有loader且在刷新时间段中则刷新,否则跳过
  23. }
  24. tryDrainReferenceQueues(); // 这个幽灵一般的操作,难受
  25. }
  26. return null;
  27. } finally {
  28. postReadCleanup();
  29. }
  30. }
  31. }

4)清理软/弱引用

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

  1. @GuardedBy("this")
  2. void preWriteCleanup(long now) { // 写前调用,其他方法类似,只是起了个不同的名字
  3. runLockedCleanup(now);
  4. }
  5. void runLockedCleanup(long now) { // 加锁+执行方法
  6. if (tryLock()) {
  7. try {
  8. drainReferenceQueues();
  9. expireEntries(now); // calls drainRecencyQueue
  10. readCount.set(0);
  11. } finally {
  12. unlock();
  13. }
  14. }
  15. }
  16. @GuardedBy("this")
  17. void drainReferenceQueues() { // 清空软/弱引用key和value
  18. if (map.usesKeyReferences()) {
  19. drainKeyReferenceQueue();
  20. }
  21. if (map.usesValueReferences()) {
  22. drainValueReferenceQueue();
  23. }
  24. }
  25. @GuardedBy("this")
  26. void drainKeyReferenceQueue() { // 清空软/弱引用key
  27. Reference<? extends K> ref;
  28. int i = 0;
  29. while ((ref = keyReferenceQueue.poll()) != null) {
  30. @SuppressWarnings("unchecked")
  31. ReferenceEntry<K, V> entry = (ReferenceEntry<K, V>) ref;
  32. map.reclaimKey(entry);
  33. if (++i == DRAIN_MAX) {
  34. break;
  35. }
  36. }
  37. }
  38. }
  39. // 之前一直没想明白的地方就是,这个keyReferenceQueue到底是什么时候被塞进去元素的???
  40. // 需要看下创建entry的时候的操作!!!抽丝剥茧就能知道了
  41. public class ReentrantLock implements Lock, java.io.Serializable {
  42. private Sync sync;
  43. public boolean tryLock() {
  44. return sync.nonfairTryAcquire(1);
  45. }
  46. abstract static class Sync extends AbstractQueuedSynchronizer {
  47. private static final long serialVersionUID = -5179523762034025860L;
  48. abstract void lock();
  49. final boolean nonfairTryAcquire(int acquires) {
  50. final Thread current = Thread.currentThread(); // 获取当前线程
  51. int c = getState();
  52. if (c == 0) { // 无线程持有,即无锁状态
  53. if (compareAndSetState(0, acquires)) { // 设置持有线程
  54. setExclusiveOwnerThread(current);
  55. return true;
  56. }
  57. }
  58. else if (current == getExclusiveOwnerThread()) { // 如果持有者就是当前线程,perfect
  59. int nextc = c + acquires;
  60. if (nextc < 0) // overflow
  61. throw new Error("Maximum lock count exceeded");
  62. setState(nextc);
  63. return true;
  64. }
  65. return false;
  66. }
  67. }
  68. }

Guava Cache 缓存实现与源码分析的更多相关文章

  1. Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析

    Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析 Volley之所以高效好用,一个在于请求重试策略,一个就在于请求结果缓存. 通过上一篇文章http://www.cnblogs.com ...

  2. guava限流器RateLimiter原理及源码分析

    前言 RateLimiter是基于令牌桶算法实现的一个多线程限流器,它可以将请求均匀的进行处理,当然他并不是一个分布式限流器,只是对单机进行限流.它可以应用在定时拉取接口数据, 预防单机过大流量使用. ...

  3. Java 自动拆箱 装箱 包装类的缓存问题--结合源码分析

    都0202 了 java 1.8 已经是主流 自动装箱 .拆箱已经很普遍使用了,那么有时候是不是会遇到坑呢? 我们先来看一段代码: public class TestWraperClass { pub ...

  4. Guava cacha 机制及源码分析

    1.ehcahce 什么时候用比较好:2.问题:当有个消息的key不在guava里面的话,如果大量的消息过来,会同时请求数据库吗?还是只有一个请求数据库,其他的等待第一个把数据从DB加载到Guava中 ...

  5. 开源分布式数据库中间件MyCat源码分析系列

    MyCat是当下很火的开源分布式数据库中间件,特意花费了一些精力研究其实现方式与内部机制,在此针对某些较为重要的源码进行粗浅的分析,希望与感兴趣的朋友交流探讨. 本源码分析系列主要针对代码实现,配置. ...

  6. Kubernetes client-go DeltaFIFO 源码分析

    概述Queue 接口DeltaFIFO元素增删改 - queueActionLocked()Pop()Replace() 概述 源码版本信息 Project: kubernetes Branch: m ...

  7. Guava 源码分析(Cache 原理 对象引用、事件回调)

    前言 在上文「Guava 源码分析(Cache 原理)」中分析了 Guava Cache 的相关原理. 文末提到了回收机制.移除时间通知等内容,许多朋友也挺感兴趣,这次就这两个内容再来分析分析. 在开 ...

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

    Guava 源码分析之Cache的实现原理 前言 Google 出的 Guava 是 Java 核心增强的库,应用非常广泛. 我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Goog ...

  9. ABP源码分析十三:缓存Cache实现

    ABP中有两种cache的实现方式:MemroyCache 和 RedisCache. 如下图,两者都继承至ICache接口(准确说是CacheBase抽象类).ABP核心模块封装了MemroyCac ...

随机推荐

  1. Django---Django的ORM的一对多操作(外键操作),ORM的多对多操作(关系管理对象),ORM的分组聚合,ORM的F字段查询和Q字段条件查询,Django的事务操作,额外(Django的终端打印SQL语句,脚本调试)

    Django---Django的ORM的一对多操作(外键操作),ORM的多对多操作(关系管理对象),ORM的分组聚合,ORM的F字段查询和Q字段条件查询,Django的事务操作,额外(Django的终 ...

  2. FreeRTOS 任务通知

    可以替代队列.二值信号量.计数型信号量和事件标志组 发送任务通知 获取任务通知 FreeRTOS 任务通知模拟二值信号量 FreeRTOS 任务通知模拟计数型信号量 FreeRTOS 任务通知模拟消息 ...

  3. Java集合学习(3):HashSet

    一.概述 HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特别是它不保证该顺序恒久不变.此类允许使用null元素. HashSet是基于Has ...

  4. python之路第三天

    2018年 7月 13 日 while循环语句: 输出1-5的数 s = 1 whlie s < 6: print(s) 用while循环输入 1 2 3 4 5 6  8 9 10 while ...

  5. jperf windows

    jperf windows版是款简单实用的网络性能测试的工具:它也是款图形界面的iperf程序,可以这进行使用JPerf程序的时候,快速的进行简化您的命令行参数,而且这进行测试结束之后,还是以图形化的 ...

  6. Prometheus(五):Prometheus+Alertmanager 配置企业微信报警

    此处默认已安装Prometheus服务,服务地址:192.168.56.200  一.设置企业微信 1.1.企业微信注册(已有企业微信账号请跳过) 企业微信注册地址:https://work.weix ...

  7. linux命令实现在当前文件夹下面模糊搜索文件

    在当前文件中查找包含的字符串 find . -name "*.txt" | xargs grep 'abc' ,例如:查找txt文件中包含字符串a的字符串

  8. Vim 简易配置

    Macbook终端vim使用系统剪切板 系统自带的, 可执行程序是 /usr/bin/vim, 安装目录是 /usr/share/vim/, 版本7.3. 我使用 homebrew 后顺手安装了一次 ...

  9. ztree的添加、修改、删除及前后台交互

    一.引入资源下载并引入ztree的相关js,css和img等.http://www.treejs.cn/v3/api.php ztree的核心代码jquery.ztree.core.jsztree关于 ...

  10. 还是把这道kmp的题po出来吧,省的以后自己也忘了

    做了一个问题突然想到可以用Kmp解决,所以看了一下自己之前写的关于Kmp的博客用JAVA实现的KMP匹配子串,记录一下,省的又忘了. /* *题目描述: * 假定我们都知道非常高效的算法来检查一个单词 ...