guava缓存原理--get操作

本文详细解析了Guava缓存的内部实现机制,包括如何通过分段的hash表和ReferenceEntry处理缓存的读取、加载及更新,探讨了缓存命中、未命中及数据刷新的流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
    int hash = hash(checkNotNull(key));
    //这里使用了分段的hash表,原理参考ConcurrentHashMap
    return segmentFor(hash).get(key, hash, loader);
  }
V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
      checkNotNull(key);
      checkNotNull(loader);
      try {
        /**
         * count表示目前java进程中活跃的缓存key的个数,
         * 每做一次put会加一,失效一个则减一
         * 为volatile变量,变量的改变对各个线程立即可变
         * 不为0则表示本地缓存有有活跃的key
         * 这样可以直接查询本地缓存获取数据
         */
        if (count != 0) { // read-volatile
          ReferenceEntry<K, V> e = getEntry(key, hash);
          if (e != null) { //能够查询到hash对应的entry,否则也需要load
            long now = map.ticker.read(); //获取当前时间
            //从entry中获取数据,如果entry无效、数据不存在、数据正在load、已过期则返回null
            V value = getLiveValue(e, now);
            if (value != null) {
              recordRead(e, now); //读取的埋点统计
              statsCounter.recordHits(1); //缓存命中统计
              //处理刷新逻辑,如果配置了在写入或读取多久以后刷新,满足条件的话,这里会重新load刷新数据
              // (注意原来数据并未过期)
              return scheduleRefresh(e, key, hash, value, now, loader);
            }
            //如果entry数据正在load,就等待load完成再获取数据
            ValueReference<K, V> valueReference = e.getValueReference();
            if (valueReference.isLoading()) {
              /**
               * 等待获取数据的逻辑根据引用不同略有不同
               * guava分别实现了强引用、软引用、弱引用、虚引用
               */
              return waitForLoadingValue(e, key, valueReference);
            }
          }
        }

        /**
         * 本地缓存中的key不存在或者已过期,此时需要调用业务的load方法加载key对应的value
         * 这里需要加锁,防止并发获取不一致的数据
         */
        return lockedGetOrLoad(key, hash, loader);
      } catch (ExecutionException ee) {
        Throwable cause = ee.getCause();
        if (cause instanceof Error) {
          throw new ExecutionError((Error) cause);
        } else if (cause instanceof RuntimeException) {
          throw new UncheckedExecutionException(cause);
        }
        throw ee;
      } finally {
        //读取数目统计,清理资源
        postReadCleanup();
      }
    }

缓存不命中时调用用户load方法获取数据

V lockedGetOrLoad(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
      ReferenceEntry<K, V> e;
      ValueReference<K, V> valueReference = null;
      LoadingValueReference<K, V> loadingValueReference = null;
      boolean createNewEntry = true;

      lock(); //加锁 此处为可重入锁
      try {
        // re-read ticker once inside the lock
        long now = map.ticker.read();
        preWriteCleanup(now);

        int newCount = this.count - 1; //???
        AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
        int index = hash & (table.length() - 1);
        ReferenceEntry<K, V> first = table.get(index);

        //哈希表每个节点有一个链表
        //使用分离链接法处理冲突,所有要进行链表遍历,一直找到给定的key为止
        for (e = first; e != null; e = e.getNext()) {
          K entryKey = e.getKey();
          if (e.getHash() == hash
              && entryKey != null
              && map.keyEquivalence.equivalent(key, entryKey)) {
            valueReference = e.getValueReference();
            if (valueReference.isLoading()) {//正在load中
              createNewEntry = false;
            } else {
              V value = valueReference.get();
              if (value == null) {
                enqueueNotification(
                    entryKey, hash, value, valueReference.getWeight(), RemovalCause.COLLECTED);
              } else if (map.isExpired(e, now)) {
                // This is a duplicate check, as preWriteCleanup already purged expired
                // entries, but let's accomodate an incorrect expiration queue.
                enqueueNotification(
                    entryKey, hash, value, valueReference.getWeight(), RemovalCause.EXPIRED);
              } else {
                recordLockedRead(e, now);
                statsCounter.recordHits(1);
                // we were concurrent with loading; don't consider refresh
                return value;
              }

              // immediately reuse invalid entries
              writeQueue.remove(e);
              accessQueue.remove(e);
              this.count = newCount; // write-volatile
            }
            break;
          }
        }

        if (createNewEntry) {
          loadingValueReference = new LoadingValueReference<>();

          if (e == null) { //新构建节点
            e = newEntry(key, hash, first);
            e.setValueReference(loadingValueReference);
            table.set(index, e);
          } else {
            e.setValueReference(loadingValueReference);
          }
        }
      } finally {
        unlock();
        postWriteCleanup();
      }

      if (createNewEntry) { //同步调用load方法获取用户提供的value
        try {
          // Synchronizes on the entry to allow failing fast when a recursive load is
          // detected. This may be circumvented when an entry is copied, but will fail fast most
          // of the time.
          synchronized (e) {
            return loadSync(key, hash, loadingValueReference, loader);
          }
        } finally {
          statsCounter.recordMisses(1);
        }
      } else {
        // The entry already exists. Wait for loading.
        return waitForLoadingValue(e, key, valueReference);
      }
    }

缓存正在load时等待获取数据

com.google.common.cache.LocalCache.Segment#waitForLoadingValue
com.google.common.cache.LocalCache.ValueReference#waitForValue
根据不同的引用类型获取数据逻辑略有不同
默认为feature.get()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值