[由零开始]Guava Cache高级实战

GuavaCache高级实战之并发操作

并发设置

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

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

concurrencyLevel=Segment数组的长度
同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();

GuavaCache高级实战之动态加载

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

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

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

GuavaCache高级实战之自定义LRU算法

public class LinkedHashLRUcache<k, v> {
  /**
    * LinkedHashMap(自身实现了LRU算法)
    * 有序
    * 每次访问一个元素,都会加到尾部
    */
    int limit;
    LRUcache<k, v> internalLRUcache;
    
    public LinkedHashLRUcache(int limit) {
        this.limit = limit;
        this.internalLRUcache = new LRUcache(limit);
        }
        public void put(k key, v value) {
        this.internalLRUcache.put(key, value);
        }
        public v get(k key) {
        return this.internalLRUcache.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.internalLRUcache.values()){
        System.out.println(o.toString());
    }
}

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;
    }
}

GuavaCache高级实战之疑难问题

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();
    	}
    }
    while ((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一样。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值