Guava源码-缓存加载

391 阅读2分钟

1、guava缓存原理-get操作

org.spark_project.guava.cache.LocalCache#get(K, org.spark_project.guava.cache.CacheLoader<? super K,V>)

V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
        int hash = this.hash(Preconditions.checkNotNull(key));
        return this.segmentFor(hash).get(key, hash, loader); // @1
}

代码@1:从分段segment中获取key对应的value;

V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
            Object var16;
            try {
                if (this.count != 0) { // @1
                    LocalCache.ReferenceEntry<K, V> e = this.getEntry(key, hash);
                    if (e != null) { // @2
                        long now = this.map.ticker.read();
                        V value = this.getLiveValue(e, now); 
                        if (value != null) {
                            this.recordRead(e, now);
                            this.statsCounter.recordHits(1);
                            Object var17 = this.scheduleRefresh(e, key, hash, value, now, loader);
                            return var17;
                        }

                        LocalCache.ValueReference<K, V> valueReference = e.getValueReference();
                        if (valueReference.isLoading()) {
                            Object var9 = this.waitForLoadingValue(e, key, valueReference);
                            return var9;
                        }
                    }
                }

                var16 = this.lockedGetOrLoad(key, hash, loader); // @3
            } catch (ExecutionException var14) {
                Throwable cause = var14.getCause();
                if (cause instanceof Error) {
                    throw new ExecutionError((Error)cause);
                }

                if (cause instanceof RuntimeException) {
                    throw new UncheckedExecutionException(cause);
                }

                throw var14;
            } finally {
                this.postReadCleanup();
            }

            return var16;
        }

代码@1: count是统计segment访问的变量,put时count+1,get时count-1;

代码@2: e!=null表明从需缓存中获取,通过e.getValueReference()获取引用,guava中的引用有下面几种,基于jdk的强/软/弱引用来实现 !

代码@3: 从用户定义的load方法加载数据;

V getLiveValue(LocalCache.ReferenceEntry<K, V> entry, long now) {
            if (entry.getKey() == null) { ///如果为空,返回空
                this.tryDrainReferenceQueues();
                return null;
            } else {
                V value = entry.getValueReference().get();
                if (value == null) {
                    this.tryDrainReferenceQueues();
                    return null;
                } else if (this.map.isExpired(entry, now)) { //  @1 
                    this.tryExpireEntries(now);
                    return null;
                } else {
                    return value;
                }
            }
}

代码@1:判断entry是否过期,这里是直接在get操作时判断是否过期,并expire将entry移除;并没有使用后台线程轮询,而是采用这种惰性删除的策略,如果一直不访问,可能存在内存泄漏问题。

V lockedGetOrLoad(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
            LocalCache.ValueReference<K, V> valueReference = null;
            LocalCache.LoadingValueReference<K, V> loadingValueReference = null;
            boolean createNewEntry = true;
            this.lock();// @1

            LocalCache.ReferenceEntry e;
            try {
                long now = this.map.ticker.read();
                this.preWriteCleanup(now);
                int newCount = this.count - 1;// @2 
                AtomicReferenceArray<LocalCache.ReferenceEntry<K, V>> table = this.table;
                int index = hash & table.length() - 1;
                LocalCache.ReferenceEntry<K, V> first = (LocalCache.ReferenceEntry)table.get(index);
                for(e = first; e != null; e = e.getNext()) {
                    K entryKey = e.getKey();
                    if (e.getHash() == hash && entryKey != null && this.map.keyEquivalence.equivalent(key, entryKey)) {
                        valueReference = e.getValueReference();
                        if (valueReference.isLoading()) { 
                            createNewEntry = false;
                        } else {
                            V value = valueReference.get();
                            if (value == null) {
                                this.enqueueNotification(entryKey, hash, valueReference, RemovalCause.COLLECTED);
                            } else {
                                if (!this.map.isExpired(e, now)) {
                                    this.recordLockedRead(e, now);
                                    this.statsCounter.recordHits(1);
                                    Object var16 = value;
                                    return var16;
                                }

                                this.enqueueNotification(entryKey, hash, valueReference, RemovalCause.EXPIRED);
                            }

                            this.writeQueue.remove(e);
                            this.accessQueue.remove(e);
                            this.count = newCount;
                        }
                        break;
                    }
                }

                if (createNewEntry) {// @3 
                    loadingValueReference = new LocalCache.LoadingValueReference();
                    if (e == null) {
                        e = this.newEntry(key, hash, first);
                        e.setValueReference(loadingValueReference); 
                        table.set(index, e);
                    } else {
                        e.setValueReference(loadingValueReference);
                    }
                }
            } finally {
                this.unlock();
                this.postWriteCleanup();
            }

            if (createNewEntry) { // @4 
                Object var19;
                try {
                    synchronized(e) {
                        var19 = this.loadSync(key, hash, loadingValueReference, loader);
                    }
                } finally {
                    this.statsCounter.recordMisses(1);
                }

                return var19;
            } else {
                return this.waitForLoadingValue(e, key, valueReference);
            }
        }

代码@1 segment是一个继承了ReentrantLock的锁,这里进行加锁来解决缓存穿透

代码@2 对count-1,上面提过count是统计key的活跃度

代码@3 创建一个新的引用,entry持有一个它

代码@4:同步调用load方法获取用户提供的value,并将value赋值给loadingValueReference

V loadSync(K key, int hash, LocalCache.LoadingValueReference<K, V> loadingValueReference, CacheLoader<? super K, V> loader) throws ExecutionException {
            ListenableFuture<V> loadingFuture = loadingValueReference.loadFuture(key, loader); //@1
            return this.getAndRecordStats(key, hash, loadingValueReference, loadingFuture);
}

代码@1: 加载用户设置的value,并赋值到loadingValueReference

public ListenableFuture<V> loadFuture(K key, CacheLoader<? super K, V> loader) {
            this.stopwatch.start();
            Object previousValue = this.oldValue.get();
            try {
                if (previousValue == null) {
                    V newValue = loader.load(key); //  @1
                    return (ListenableFuture)(this.set(newValue) ? this.futureValue : Futures.immediateFuture(newValue)); //  @2
                } else {
                    ListenableFuture<V> newValue = loader.reload(key, previousValue);
                    return newValue != null ? newValue : Futures.immediateFuture((Object)null);
                }
            } catch (Throwable var5) {
            }
        }

代码@1 加载用户的value

代码@2 赋值给并赋值到loadingValueReference