Guava缓存器源码分析——数据查询

来源:互联网 发布:单片机毕业设计课题 编辑:程序博客网 时间:2024/05/18 17:02
Guava缓存器的数据查询函数调用:
    get(K) ——
           localCache.getOrLoad(key) —— 
                   get(key, defaultLoader)(如果创建的为Cache缓存,defaultLoader设为null)
具体步骤:
1、计算key的哈希值。
      LocalCache会使用分段锁来保护不同段的数据,在查询或插入元素时,需要通过哈希算法定位到Segment, LocalCache 会使用变种Hash算法Wang/Jenkins 对元素的hashCode进行一次再哈希 :
 static int rehash(int h)  {
        h += (h << 15) ^ 0xffffcd7d;
        h ^= (h >>> 10);
        h += (h << 3);
        h ^= (h >>> 6);
        h += (h << 2) + (h << 14);
        return h ^ (h >>> 16);
    }
  再哈希的目的是为了减少哈希冲突,使元素能够比较均匀的分布在不同的Segment上,从而提高缓存器的存取效率。如果哈希的质量太差,元素集中哈希到少数Segment中,不仅存取元素缓慢,分段锁也会失去意义。

2、通过哈希值,获取数据段。
     segments[(hash >>> segmentShift) & segmentMask]
      下面分析下初始化segmentShift、segmentMask及segments的代码:
             //缓存器支持的最大段数MAX_SEGMENTS 为1 << 16,如果在builder中没有设置concurrencyLevel的大小,默认为4。
             concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS);
             //一般计算出一个大于或等于concurrencyLevel的最小的2的N次方值来作为segments数组的长度,如果设置了maxWeight,segmentCount 还需满足segmentCount  * 20 <= maxWeight ,此时假设每个段平均哈希到20个元素,再少就不继续增加段了:
             int segmentShift = 0;
             int segmentCount = 1;
             while (segmentCount < concurrencyLevel
                   && (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
                   ++segmentShift;
                   segmentCount <<= 1;
             }
             this.segmentShift = 32 - segmentShift;
             segmentMask = segmentCount - 1;
             this.segments = newSegmentArray(segmentCount);
     假如concurrencyLevel等于14或15, segmentCount 等于16,则缓存器里锁的个数也是16。

3、调用数据段的get方法。
     V get(K key, int hash, CacheLoader<? super K, V> loader)
     1)获取键值的引用: ReferenceEntry<K, V> e = getEntry(key, hash);
           获取值:V value = getLiveValue(e, now);
           其中判断数据是否过期的方法:
            boolean isExpired(ReferenceEntry<K, V> entry, long now) {
                checkNotNull(entry);
                if (expiresAfterAccess()
                    && (now - entry.getAccessTime() >= expireAfterAccessNanos)) {
                      return true;
                }
                if (expiresAfterWrite()
                    && (now - entry.getWriteTime() >= expireAfterWriteNanos)) {
                  return true;
                }
                return false;
          }
          如果获取的值不为空,则需要将此次访问加入到访问队列(Queue<ReferenceEntry<K, V>>)中,当缓存器满时,用来选择需要清出的数据,同时记录命中信息。
          如果获取的值正在加载(valueReference.isLoading()),则需等待其加载完后返回。
     2)键值引用为空,则需要加载。
          lockedGetOrLoad(key, hash, loader);
          数据的加载过程会上ReentrantLock锁(Segment继承至ReentrantLock),同时会重新进行验证:如果缓存值正在加载,则无需进行下一步操作;若果缓存值没在加载,且缓存值已存在于缓存且没有过期,则返回该值;验证完才开始同步加载。
  
     Guava缓存器将缓存数据分为一个个段,每个段中的数据实际存放在volatile AtomicReferenceArray<ReferenceEntry<K, V>> table中, ReferenceEntry接口中定义了许多与键值相关的函数,比如获取访问时间、写时间、键、值等, Guava缓存器在数据查询时,考虑得很全面,以后遇到多线程环境下数据读写的问题,可以借鉴这里面的思想。
原创粉丝点击