Guava的LoadingCache

一、使用
1)简单初始化:

    	LoadingCache loadCache = CacheBuilder.newBuilder()
                .expireAfterWrite(6, TimeUnit.SECONDS)
                .maximumSize(100)
                .build(new CacheLoader() {
                    @Override
                    public Long load(Long userId) throws Exception {
                        return getNum(userId);
                    }
                });

2)异步更新的初始化:

    	LoadingCache loadCache = CacheBuilder.newBuilder()
                .expireAfterWrite(6, TimeUnit.SECONDS)
                .refreshAfterWrite(2, TimeUnit.SECONDS)
                .maximumSize(100)
                .build(CacheLoader.asyncReloading(new CacheLoader() {
                    @Override
                    public Long load(Long userId) throws Exception {
                        return getNum(userId);
                    }
                },Executors.newFixedThreadPool(3)));

二、源码
首先,LoadingCache是由调用触发的刷新值,而并不会定时调度主动触发。所以在请求的时候顺便验证当前值是否有效期超过refreshNanos(即refreshAfterWrite的值),是则调用refresh,这时候更新操作会标记一下状态,以便于其他后续的线程调用refresh时,不重复创建。然后具体refresh操作执行,根据是否第一次区分调用load或reload,这个见下文分析。

1)get方法:因为build里面使用是LocalLoadingCache类,所以这里看它的源码。
最终调用时Segment.get方法:

  • 首先查询旧值,不存在则加锁更新返回,这时候其他get线程且没有获取旧值情况下会阻塞。
  • 查询到Entry时,查询是否存在旧值,有则验证是否过期以便于刷新(刷新逻辑加下一条代码),无过期直接返回。不存在旧值时,则需要验证是否有线程已加锁load,有则等待,无则加锁load。
    V get(K key, int hash, CacheLoader loader) throws ExecutionException {
      checkNotNull(key);
      checkNotNull(loader);
      try {
        if (count != 0) { // read-volatile
          // don't call getLiveEntry, which would ignore loading values
          ReferenceEntry e = getEntry(key, hash);
          if (e != null) {
            long now = map.ticker.read();
            V value = getLiveValue(e, now);
            if (value != null) {
              recordRead(e, now);
              statsCounter.recordHits(1);
              return scheduleRefresh(e, key, hash, value, now, loader);
            }
            ValueReference valueReference = e.getValueReference();
            if (valueReference.isLoading()) {
              return waitForLoadingValue(e, key, valueReference);
            }
          }
        }

        // at this point e is either null or expired;
        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();
      }
    }

2)scheduleRefresh方法:

  • 判断该旧值是否过期,未过期直接返回,过期则尝试refresh(相当于主动调起Segment.refresh(…))。尝试标记当前线程为load状态,已存在则返回。标记成功则开始load。
  • 其中load的过程,LoadingValueReference.loadFuture(key,
    loader);中,如果是第一次就load,如果不是第一次,则异步reload(执行初始化时候CacheLoader的reload()),就会调用你自己定义的CacheLoader的reload方法,如果是异步的那就是异步的,如果是同步那便是同步的。
    public ListenableFuture loadFuture(K key, CacheLoader loader) {
      stopwatch.start();
      V previousValue = oldValue.get();
      try {
        if (previousValue == null) {
          V newValue = loader.load(key);
          return set(newValue) ? futureValue : Futures.immediateFuture(newValue);
        }
        ListenableFuture newValue = loader.reload(key, previousValue);
        if (newValue == null) {
          return Futures.immediateFuture(null);
        }
        // To avoid a race, make sure the refreshed value is set into loadingValueReference
        // *before* returning newValue from the cache query.
        return Futures.transform(newValue, new Function() {
          @Override
          public V apply(V newValue) {
            LoadingValueReference.this.set(newValue);
            return newValue;
          }
        });
      } catch (Throwable t) {
        if (t instanceof InterruptedException) {
          Thread.currentThread().interrupt();
        }
        return setException(t) ? futureValue : fullyFailedFuture(t);
      }
    }

你可能感兴趣的:(javaSE)