Guava Cache原理_guava cache原理 csdn-程序员宅基地

技术标签: 缓存  

基本用法

构建

    public  static void initCache(LoadingCache cache) throws ExecutionException {
    

        for(int i =1;i<=3;i++){
    
            //连接数据源 ,如果缓存没有就读取数据源
            cache.get(String.valueOf(i));
        }

    }

    /**
     * 获得当前缓存的记录
     * @param cache
     * @throws Exception
     */
    public static void displayCache(LoadingCache cache)throws Exception{
    

        Iterator its=cache.asMap().entrySet().iterator();
        while(its.hasNext()){
    
            System.out.println(its.next().toString());
        }




    }


    public static void main(String[] args) throws Exception {
    

        //CacheLoader方式
        LoadingCache<String,Object>  cache=CacheBuilder.newBuilder()
                .build(new CacheLoader<String, Object>() {
    
                    @Override
                    public Object load(String key) throws Exception {
    

                        return Constants.hm.get(key);
                    }
                });

        //初始化
        initCache(cache);
  • 另一种
 LoadingCache<String,Object>  cache=CacheBuilder.newBuilder()
                .build(new CacheLoader<String, Object>() {
    
                    @Override
                    public Object load(String key) throws Exception {
    

                        return Constants.hm.get(key);
                    }
                });

其它用法

//CacheLoader方式
        LoadingCache<String,Object>  cache=CacheBuilder.newBuilder()
                //最大个数3
                .maximumSize(3)
                //统计
                .recordStats()
                .removalListener(new RemovalListener<Object, Object>() {
    
                    @Override
                    public void onRemoval(RemovalNotification<Object, Object> notification) {
    
                        System.out.println(" onRemoval key  "+notification.getKey()+" 原因: "+notification.getCause());
                    }
                })
                .build(new CacheLoader<String, Object>() {
    
                    @Override
                    public Object load(String key) throws Exception {
    

                        return Constants.hm.get(key);
                    }
                });

        //初始化
        initCache(cache);
        System.out.println(cache.size());
        Object o1 = cache.getIfPresent("1");
        System.out.println("========================"+o1);
        displayCache(cache);
        Object o = get("4", cache);
        System.out.println("========================");
        displayCache(cache);
        System.out.println(cache.stats());

删除1

  //CacheLoader方式
        LoadingCache<String,Object>  cache=CacheBuilder.newBuilder()
                //最大个数3
                .maximumSize(3)
                .expireAfterAccess(3, TimeUnit.SECONDS)
                //统计
                .recordStats()
                .removalListener(new RemovalListener<Object, Object>() {
    
                    @Override
                    public void onRemoval(RemovalNotification<Object, Object> notification) {
    
                        System.out.println(" onRemoval key  "+notification.getKey()+" 原因: "+notification.getCause());
                    }
                })
                .build(new CacheLoader<String, Object>() {
    
                    @Override
                    public Object load(String key) throws Exception {
    

                        return Constants.hm.get(key);
                    }
                });

        //初始化
        initCache(cache);
        //显示缓存数据
        displayCache(cache);
        Thread.sleep(1000);
        //访问1
        cache.getIfPresent("1");

        //歇了2.1秒
        Thread.sleep(2100);

        System.out.println("==================================");
        displayCache(cache);

删除2

  //CacheLoader方式
        LoadingCache<String,Object>  cache=CacheBuilder.newBuilder()
                //最大个数3
                .maximumSize(3)
                .expireAfterWrite(3, TimeUnit.SECONDS)
                //统计
                .recordStats()
                .removalListener(new RemovalListener<Object, Object>() {
    
                    @Override
                    public void onRemoval(RemovalNotification<Object, Object> notification) {
    
                        System.out.println(" onRemoval key  "+notification.getKey()+" 原因: "+notification.getCause());
                    }
                })
                .build(new CacheLoader<String, Object>() {
    
                    @Override
                    public Object load(String key) throws Exception {
    

                        return Constants.hm.get(key);
                    }
                });

        //初始化
        initCache(cache);
        //显示缓存数据
        displayCache(cache);
        Thread.sleep(1000);
        //访问1
        cache.getIfPresent("1");

        //歇了2.1秒
        Thread.sleep(2100);

        System.out.println("==================================");
        displayCache(cache);

弱key

//CacheLoader方式
        LoadingCache<String,Object>  cache=CacheBuilder.newBuilder()
                //最大个数3
                .maximumSize(3)
                .weakValues()
                //统计
                .recordStats()
                .removalListener(new RemovalListener<Object, Object>() {
    
                    @Override
                    public void onRemoval(RemovalNotification<Object, Object> notification) {
    
                        System.out.println(" onRemoval key  "+notification.getKey()+" 原因: "+notification.getCause());
                    }
                })
                .build(new CacheLoader<String, Object>() {
    
                    @Override
                    public Object load(String key) throws Exception {
    

                        return Constants.hm.get(key);
                    }
                });

        //初始化
        initCache(cache);
        //显示缓存数据
        displayCache(cache);

        Object v = new Object();
        cache.put("1",v);
        v = new Object();
        System.gc();
        System.out.println("=============");;
        displayCache(cache);

删除指定

        //CacheLoader方式
        LoadingCache<String,Object>  cache=CacheBuilder.newBuilder()
                //最大个数3
                .maximumSize(3)
                //统计
                .recordStats()
                .removalListener(new RemovalListener<Object, Object>() {
    
                    @Override
                    public void onRemoval(RemovalNotification<Object, Object> notification) {
    
                        System.out.println(" onRemoval key  "+notification.getKey()+" 原因: "+notification.getCause());
                    }
                })
                .build(new CacheLoader<String, Object>() {
    
                    @Override
                    public Object load(String key) throws Exception {
    

                        return Constants.hm.get(key);
                    }
                });

        //初始化
        initCache(cache);
        //显示缓存数据
        displayCache(cache);
        //删除所有
        //cache.invalidateAll();
        //删除指定
       // cache.invalidate("1");
        cache.invalidateAll(Arrays.asList("1","2"));
        System.out.println("=============");
        displayCache(cache);

GuavaCache核心原理之数据结构

Guava Cache的数据结构跟ConcurrentHashMap类似,但也不完全一样。最基本的区别是
ConcurrentMap会一直保存所有添加的元素,直到显式地移除。
相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。其数据结构图如下
在这里插入图片描述

  • LocalCache为Guava Cache的核心类,包含一个Segment数组组成
  • Segement数组的长度决定了cache的并发数
  • 每一个Segment使用了单独的锁,其实每个Segment继承了ReentrantLock,对Segment的写操作需要先拿到锁
  • 每个Segment由一个table和5个队列组成

5个队列

  • ReferenceQueue keyReferenceQueue : 已经被GC,需要内部清理的键引用队列
  • ReferenceQueue valueReferenceQueue : 已经被GC,需要内部清理的值引用队列
  • ConcurrentlinkedQueue<ReferenceEntry<k,v>> recencyQueue : LRU队列,当segment上达到
    临界值发生写操作时该队列会移除数据
  • Queue<ReferenceEntry<K, V>> writeQueue:写队列,按照写入时间进行排序的元素队列,写入一个元素时会把它加入到队列尾部
  • Queue<ReferenceEntry<K, V>> accessQueue:访问队列,按照访问时间进行排序的元素队列,访问(包括写入)一个元素时会把它加入到队列尾部
1个table:

AtomicReferenceArray<ReferenceEntry<K, V>> table:AtomicReferenceArray可以用原子方式
更新其元素的对象引用数组

ReferenceEntry<k,v>

ReferenceEntry是Guava Cache中对一个键值对节点的抽象,每个ReferenceEntry数组项都是一条ReferenceEntry链。并且一个ReferenceEntry包含key、hash、valueReference、next字段(单链)
Guava Cache使用ReferenceEntry接口来封装一个键值对,而用ValueReference来封装Value值

GuavaCache核心原理之回收机制

Guava Cache提供了三种基本的缓存回收方式:

  • 基于容量回收
    在缓存项的数目达到限定值之前,采用LRU的回收方式
  • 定时回收
    expireAfterAccess:缓存项在给定时间内没有被读/写访问,则回收。回收顺序和基于大小回收一样(LRU)
    expireAfterWrite:缓存项在给定时间内没有被写访问(创建或覆盖),则回收
  • 基于引用回收
    通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以垃圾回收

除了以上三种还有主动删除,采用命令,
GuavaCache构建的缓存不会"自动"执行清理和回收工作,也不会在某个缓存项过期后马上清理,也没有诸如此类的清理机制。

GuavaCache是在每次进行缓存操作的时候,惰性删除 如get()或者put()的时候,判断缓存是否过期

GuavaCache核心原理之Segment定位

通过key做hash定位到所在的Segment
通过位运算找首地址的偏移量 SegmentCount>=并发数且为2的n次方

V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
    
// 注意,key不可为空
int hash = hash(checkNotNull(key));
// 通过hash定位到segment数组的某个Segment元素,然后调用其get方法
return segmentFor(hash).get(key, hash, loader);
}

再找到segment中的Entry链数组,通过key的hash定位到某个Entry节点

V get(K key, int hash, CacheLoader<? super K, V> loader) throws
ExecutionException {
    
checkNotNull(key);
checkNotNull(loader);
try {
    
if (count != 0) {
     // read-volatile
// 内部也是通过找Entry链数组定位到某个Entry节点
ReferenceEntry<K, V> e = getEntry(key, hash);
......
GuavaCache 并发设置

GuavaCache通过设置 concurrencyLevel 使得缓存支持并发的写入和读取

LoadingCache<String,Object> cache = CacheBuilder.newBuilder()
// 最大3个 同时支持CPU核数线程写缓存
.maximumSize(3)
.concurrencyLevel(Runtime.getRuntime().availableProcessors())
.build();

concurrencyLevel=Segment数组的长度
默认长度4
同ConcurrentHashMap类似Guava cache的并发也是通过分离锁实现

V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
    
int hash = this.hash(Preconditions.checkNotNull(key));
//通过hash值确定该key位于哪一个segment上,并获取该segment
return this.segmentFor(hash).get(key, hash, loader);
}

LoadingCache采用了类似ConcurrentHashMap的方式,将映射表分为多个segment。segment之间可以并发访问,这样可以大大提高并发的效率,使得并发冲突的可能性降低了。

更新锁定

GuavaCache提供了一个refreshAfterWrite定时刷新数据的配置项
如果经过一定时间没有更新或覆盖,则会在下一次获取该值的时候,会在后台异步去刷新缓存刷新时只有一个请求回源取数据,其他请求会阻塞(block)在一个固定时间段,如果在该时间段内没有获得新值则返回旧值。

LoadingCache<String,Object> cache = CacheBuilder.newBuilder()
// 最大3个 同时支持CPU核数线程写缓存
.maximumSize(3).concurrencyLevel(Runtime.getRuntime().availableProcessors()).
//3秒内阻塞会返回旧数据
refreshAfterWrite(3,TimeUnit.SECONDS).build();

在这里插入图片描述

动态加载

动态加载行为发生在获取不到数据或者是数据已经过期的时间点,Guava动态加载使用回调模式
用户自定义加载方式,然后Guava cache在需要加载新数据时会回调用户的自定义加载方式

segmentFor(hash).get(key, hash, loader)

loader即为用户自定义的数据加载方式,当某一线程get不到数据会去回调该自定义加载方式去加载数

自定义LRU算法
public class LRUcache<k, v> extends LinkedHashMap<k, v> {
    
    private final int limit;

    public LRUcache(int limit) {
    
        //初始化 accessOrder : true 改变尾结点
        super(16, 0.75f, true);
        this.limit = limit;
    }

    //是否删除最老的数据
    @Override
    protected boolean removeEldestEntry(Map.Entry<k, v> eldest) {
    

        return size() > limit;
    }


}
// 
public class LinkedHashLRUcache<k,v> {
    
    /**
     * LinkedHashMap(自身实现了LRU算法)
     * 有序
     * 每次访问一个元素,都会加到尾部
     */
    int limit;
    LRUcache<k, v> lruCache;


    public LinkedHashLRUcache(int limit) {
    

        this.limit = limit;
        this.lruCache = new LRUcache(limit);
    }


    public void put(k key, v value) {
    

        this.lruCache.put(key, value);
    }

    public v get(k key) {
    

        return this.lruCache.get(key);
    }


    public static void main(String[] args) {
    
        LinkedHashLRUcache lru = new LinkedHashLRUcache(3);
        lru.put(1, "zhangfei1");
        lru.put(2, "zhangfei2");
        lru.put(3, "zhangfei3");
        lru.get(1);
        lru.put(4, "zhangfei4");
        for (Object o : lru.lruCache.values()) {
    
            System.out.println(o.toString());
        }
    }
}

GuavaCache会oom(内存溢出)吗

会,当我们设置缓存永不过期(或者很长),缓存的对象不限个数(或者很大)时,比如:

Cache<String, String> cache = CacheBuilder.newBuilder()
.expireAfterWrite(100000, TimeUnit.SECONDS)
.build();

不断向GuavaCache加入大字符串,最终将会oom
解决方案:缓存时间设置相对小些,使用弱引用方式存储对象

Cache<String, String> cache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.SECONDS)
.weakValues().build();
GuavaCache缓存到期就会立即清除吗

不是的,GuavaCache是在每次进行缓存操作的时候,如get()或者put()的时候,判断缓存是否过期

void evictEntries(ReferenceEntry<K, V> e) {
    
drainRecencyQueue();
while ((e = writeQueue.peek()) != null && map.isExpired(e, now)) {
    
if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
    
throw new AssertionError();
}
} w
hile ((e = accessQueue.peek()) != null && map.isExpired(e, now)) {
    
if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
    
throw new AssertionError();
}
}
}

一个如果一个对象放入缓存以后,不在有任何缓存操作(包括对缓存其他key的操作),那么该缓存不会主动过期的

GuavaCache如何找出最久未使用的数据

用accessQueue,这个队列是按照LRU的顺序存放的缓存对象(ReferenceEntry)的。会把访问过的对象放到队列的最后。
并且可以很方便的更新和删除链表中的节点,因为每次访问的时候都可能需要更新该链表,放入到链表
的尾部。
这样,每次从access中拿出的头节点就是最久未使用的。
对应的writeQueue用来保存最久未更新的缓存队列,实现方式和accessQueue一样。

结构

  • CacheBuilder:类,缓存构建器。构建缓存的入口,指定缓存配置参数并初始化本地缓存。
    CacheBuilder在build方法中,会把前面设置的参数,全部传递给LocalCache,它自己实际不参与任何计算
  • CacheLoader:抽象类。用于从数据源加载数据,定义load、reload、loadAll等操作
  • Cache:接口,定义get、put、invalidate等操作,这里只有缓存增删改的操作,没有数据加载的操作
  • LoadingCache:接口,继承自Cache。定义get、getUnchecked、getAll等操作,这些操作都会从数据源load数据
  • LocalCache:类。整个guava cache的核心类,包含了guava cache的数据结构以及基本的缓存的操作方法
  • LocalManualCache:LocalCache内部静态类,实现Cache接口。其内部的增删改缓存操作全部调用成员变量localCache(LocalCache类型)的相应方法
  • LocalLoadingCache:LocalCache内部静态类,继承自LocalManualCache类,实现
  • LoadingCache接口。其所有操作也是调用成员变量localCache(LocalCache类型)的相应方法
源码之LocalCache

LoadingCache这些类表示获取Cache的方式,可以有多种方式,但是它们的方法最终调用到LocalCache的方法,LocalCache是Guava Cache的核心类

@GwtCompatible(emulated = true)
class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
    

LocalCache为Guava Cache的核心类 LocalCache的数据结构与ConcurrentHashMap很相似,都由多个segment组成,且各segment相对独立,互不影响,所以能支持并行操作

//Map的数组
final Segment<K, V>[] segments;
//并发量,即segments数组的大小
final int concurrencyLevel;
...
//访问后的过期时间,设置了expireAfterAccess就有
final long expireAfterAccessNanos;
//写入后的过期时间,设置了expireAfterWrite就有
final long expireAfterWriteNa就有nos;
//刷新时间,设置了refreshAfterWrite就有
final long refreshNanos;
//removal的事件队列,缓存过期后先放到该队列
final Queue<RemovalNotification<K, V>> removalNotificationQueue;
//设置的removalListener
final RemovalListener<K, V> removalListener;
....
源码之 Segment

每个segment由一个table和若干队列组成。缓存数据存储在table中,其类型为AtomicReferenceArray

 static class Segment<K, V> extends ReentrantLock {
    
 /**
* segments 维护一个entry列表的table,确保一致性状态。所以可以不加锁去读。节点的
next field是不可修改的final,因为所有list的增加操作
*/
final LocalCache<K, V> map;
/**
* 该segment区域内所有存活的元素个数
*/
volatile int count;
/**
* 改变table大小size的更新次数。这个在批量读取方法期间保证它们可以看到一致性的快照:
* 如果modCount在我们遍历段加载大小或者核对containsValue期间被改变了,然后我们会看
到一个不一致的状态视图,以至于必须去重试。
* count+modCount 保证内存一致性
* *
感觉这里有点像是版本控制,比如数据库里的version字段来控制数据一致性
*/
int modCount;

/**
* 每个段表,使用乐观锁的Array来保存entry The per-segment table.
*/
volatile AtomicReferenceArray<ReferenceEntry<K, V>> table; // 这里和
concurrentHashMap不一致,原因是这边元素是引用,直接使用不会线程安全
/**
* A queue of elements currently in the map, ordered by write time.
Elements are added to the tail of the queue
* on write.
*/
@GuardedBy("Segment.this")
final Queue<ReferenceEntry<K, V>> writeQueue;
/**
* A queue of elements currently in the map, ordered by access time.
Elements are added to the tail of the queue
* on access (note that writes count as accesses).
*/
@GuardedBy("Segment.this")
final Queue<ReferenceEntry<K, V>> accessQueue;
}
源码之 ReferenceEntry
@GwtIncompatible
interface ReferenceEntry<K, V> {
    
  /** Returns the value reference from this entry. */
  ValueReference<K, V> getValueReference();

  /** Sets the value reference for this entry. */
  void setValueReference(ValueReference<K, V> valueReference);

  /** Returns the next entry in the chain. */
  @Nullable
  ReferenceEntry<K, V> getNext();

  /** Returns the entry's hash. */
  int getHash();

  /** Returns the key for this entry. */
  @Nullable
  K getKey();

  /*
   * Used by entries that use access order. Access entries are maintained in a doubly-linked list.
   * New entries are added at the tail of the list at write time; stale entries are expired from
   * the head of the list.
   */

  /** Returns the time that this entry was last accessed, in ns. */
  @SuppressWarnings("GoodTime")
  long getAccessTime();

  /** Sets the entry access time in ns. */
  @SuppressWarnings("GoodTime") // b/122668874
  void setAccessTime(long time);

  /** Returns the next entry in the access queue. */
  ReferenceEntry<K, V> getNextInAccessQueue();

  /** Sets the next entry in the access queue. */
  void setNextInAccessQueue(ReferenceEntry<K, V> next);

  /** Returns the previous entry in the access queue. */
  ReferenceEntry<K, V> getPreviousInAccessQueue();

  /** Sets the previous entry in the access queue. */
  void setPreviousInAccessQueue(ReferenceEntry<K, V> previous);

  /*
   * Implemented by entries that use write order. Write entries are maintained in a doubly-linked
   * list. New entries are added at the tail of the list at write time and stale entries are
   * expired from the head of the list.
   */

  @SuppressWarnings("GoodTime")
  /** Returns the time that this entry was last written, in ns. */
  long getWriteTime();

  /** Sets the entry write time in ns. */
  @SuppressWarnings("GoodTime") // b/122668874
  void setWriteTime(long time);

  /** Returns the next entry in the write queue. */
  ReferenceEntry<K, V> getNextInWriteQueue();

  /** Sets the next entry in the write queue. */
  void setNextInWriteQueue(ReferenceEntry<K, V> next);

  /** Returns the previous entry in the write queue. */
  ReferenceEntry<K, V> getPreviousInWriteQueue();

  /** Sets the previous entry in the write queue. */
  void setPreviousInWriteQueue(ReferenceEntry<K, V> previous);
}


源码之CacheBuilder

缓存构建器。构建缓存的入口,指定缓存配置参数并初始化本地缓存。
主要采用builder的模式,CacheBuilder的每一个方法都返回这个CacheBuilder知道build方法的调用。
注意build方法有重载,带有参数的为构建一个具有数据加载功能的缓存,不带参数的构建一个没有数
据加载功能的缓存
build方法会创建LocalCache

  public <K1 extends K, V1 extends V> LoadingCache<K1, V1> build(
      CacheLoader<? super K1, V1> loader) {
    
    checkWeightWithWeigher();
    return new LocalCache.LocalLoadingCache<>(this, loader);
  }


源码剖析之Put流程

1、上锁
2、清除队列元素
清理的是keyReferenceQueue和valueReferenceQueue这两个队列,这两个队列是引用队列
如果发现key或者value被GC了,那么会在put的时候触发清理
3、setvalue方法了,它做的是将value写入Entry

  @Override
  public V put(K key, V value) {
    
    checkNotNull(key);
    checkNotNull(value);
    int hash = hash(key);
    return segmentFor(hash).put(key, hash, value, false);
  }
  • segmentFor
 Segment<K, V> segmentFor(int hash) {
    
    // TODO(fry): Lazily create segments?
    return segments[(hash >>> segmentShift) & segmentMask];
  }

  • put 方法
@Nullable
    V put(K key, int hash, V value, boolean onlyIfAbsent) {
    
    //保证线程安全加锁
      lock();
      try {
    
      //获取当前时间
        long now = map.ticker.read();
        //根据过期,淘汰策略 清除列队中的元素
        preWriteCleanup(now);

        int newCount = this.count + 1;
        if (newCount > this.threshold) {
     // ensure capacity
          expand();
          newCount = this.count + 1;
        }

		//获取当前Entry中的HashTable的Entry数组
        AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
        ///定位 hash
        int index = hash & (table.length() - 1);
        获取第一个元素
        ReferenceEntry<K, V> first = table.get(index);

		遍历整个Entry链表判断是否存在
        // Look for an existing entry.
        for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
    
          K entryKey = e.getKey();
          //判断hash,Value是否是同一个
          if (e.getHash() == hash&& entryKey != null&& map.keyEquivalence.equivalent(key, entryKey)) {
    
            // We found an existing entry.
			//如果找到相应的元素
            ValueReference<K, V> valueReference = e.getValueReference();
            //获取所有的值 //获取value
            V entryValue = valueReference.get();
		    //判断是否为空
            if (entryValue == null) {
    	
            //如果entry的value为null,可能被GC掉了
              ++modCount;
              if (valueReference.isActive()) {
    
              //减小锁时间的开销
                enqueueNotification(
                    key, hash, entryValue, valueReference.getWeight(), RemovalCause.COLLECTED);
                 //利用原来的key并且刷新value
                 //存储数据,并且将新增加的元素写入两个队列中,一个Write队列、一个Access队列
                setValue(e, key, value, now);
                newCount = this.count; // count remains unchanged
              } else {
    
              //存储数据,并且将新增加的元素写入两个队列
                setValue(e, key, value, now);
                newCount = this.count + 1;
              }
              // write-volatile,保证内存可见性
              this.count = newCount; // write-volatile
              //淘汰缓存
              evictEntries(e);
              return null;
            } else if (onlyIfAbsent) {
    
            //原来的Entry中包含指定key的元素,所以读取一次,读取操作需要更新Access队列
              // Mimic
              // "if (!map.containsKey(key)) ...
              // else return map.get(key);
              recordLockedRead(e, now);
              return entryValue;
            } else {
    
              // clobber existing entry, count remains unchanged
              ++modCount;
              enqueueNotification(
                  key, hash, entryValue, valueReference.getWeight(), RemovalCause.REPLACED);
                  //存储数据,并且将新增加的元素写入两个队列中
              setValue(e, key, value, now);
              //数据的淘汰
              evictEntries(e);
              return entryValue;
            }
          }
        }

        // Create a new entry.
        ++modCount;
        //如果目标的entry不存在,那么新建entry
        ReferenceEntry<K, V> newEntry = newEntry(key, hash, first);
        setValue(newEntry, key, value, now);
        table.set(index, newEntry);
        newCount = this.count + 1;
        this.count = newCount; // write-volatile
        evictEntries(newEntry);
        return null;
      } finally {
    
      //解锁
        unlock();
        //处理刚刚的remove Cause
        postWriteCleanup();
      }
    }

源码剖析之Get流程

  1. 获取对象引用(引用可能是非alive的,比如是需要失效的、比如是loading的);
  2. 判断对象引用是否是alive的(如果entry是非法的、部分回收的、loading状态、需要失效的,则认为不是alive)。
  3. 如果对象是alive的,如果设置refresh,则异步刷新查询value,然后等待返回最新value。
  4. 针对不是alive的,但却是在loading的,等待loading完成(阻塞等待)。
  5. 这里如果value还没有拿到,则查询loader方法获取对应的值(阻塞获取)。
    // LoadingCache methods
    //local cache的代理
 V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
    
    int hash = hash(checkNotNull(key));
    return segmentFor(hash).get(key, hash, loader);
  }

V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
    
//hash——>rehash
      checkNotNull(key);
      checkNotNull(loader);
      try {
    
        if (count != 0) {
     // read-volatile
          // don't call getLiveEntry, which would ignore loading values
          ReferenceEntry<K, V> 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<K, V> valueReference = e.getValueReference();
            if (valueReference.isLoading()) {
    
              return waitForLoadingValue(e, key, valueReference);
            }
          }
        }
       // 如果取不到值,那么进行统一的加锁get
		//加锁去数据源加载数据到缓存中
        // 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 {
    
      每次Put和get之后都要进行一次Clean
        postReadCleanup();
      }
    }

源码剖析之过期重载

数据过期不会自动重载,而是通过get操作时执行过期重载。具体就是CacheBuilder构造的LocalLoadingCache

 static class LocalLoadingCache<K, V> extends LocalManualCache<K, V>
      implements LoadingCache<K, V> {
    

    LocalLoadingCache(
        CacheBuilder<? super K, ? super V> builder, CacheLoader<? super K, V> loader) {
    
      super(new LocalCache<K, V>(builder, checkNotNull(loader)));
    }

    // LoadingCache methods

    @Override
    public V get(K key) throws ExecutionException {
    
      return localCache.getOrLoad(key);
    }

    @Override
    public V getUnchecked(K key) {
    
      try {
    
        return get(key);
      } catch (ExecutionException e) {
    
        throw new UncheckedExecutionException(e.getCause());
      }
    }

    @Override
    public ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException {
    
      return localCache.getAll(keys);
    }

    @Override
    public void refresh(K key) {
    
      localCache.refresh(key);
    }

    @Override
    public final V apply(K key) {
    
      return getUnchecked(key);
    }

    // Serialization Support

    private static final long serialVersionUID = 1;

    @Override
    Object writeReplace() {
    
      return new LoadingSerializationProxy<>(localCache);
    }
  }
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ko0491/article/details/109297967

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签