前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java本地内存LoadingCache介绍

Java本地内存LoadingCache介绍

作者头像
心平气和
发布2022-03-29 09:14:34
2.2K0
发布2022-03-29 09:14:34
举报
文章被收录于专栏:程序员升级之路

一、本地缓存设计思路

如果让我们去设计一个本地缓存,需要考虑以下问题:

1、数据存储结构如何设计

本地缓存应该是要能高效的查找,因此数据的存储结构很关键;

最简单的设计就是一个大的HashMap,这样在多线程写的时候会有问题,当然也可以用并发场景下高性能的ConcurrencyHashMap;当然还可以自己设计底层的存储结构;

2、如何限制本地缓存的大小

为什么要限制,因为内存是宝贵的资源,作为容错处理必须有相应的参数设置本地缓存占用内存大小,具体来说是按内存大小,还是其它维度?

3、更新问题

实际场景缓存的内容是有过期时间的,即过一段时间需要更新,具体是通过过期时间的方式还是刷新机制

4、并发问题

当缓存快要过期了,如果这个时候多线程来取数据,是有一个线程能更新还是随机?

二、LoadingCache介绍

LoadingCache是大名鼎鼎的Google的Guava包的一个本地缓存接口(Guava是一个优秀的类库,具体就不在这里介绍了,有兴趣的同学可以自己百度下),定义如下:

代码语言:javascript
复制
@GwtCompatible
public interface LoadingCache<K, V> extends Cache<K, V>, Function<K, V> {
  V get(K key) throws ExecutionException;

  V getUnchecked(K key);

  ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException;
  @Deprecated
  @Override
  V apply(K key);
  
  void refresh(K key);

  @Override
  ConcurrentMap<K, V> asMap();
}

可以看到这是一个泛型接口,其中K为key的类型,V为缓存内容的类型,如果我想以String为Key,缓存内容也是String,则这样声明:

代码语言:javascript
复制
LoadingCache<String,  String> actCache;

来看看LoadingCache的具体使用

1、引入依赖

代码语言:javascript
复制
<dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>21.0</version>
</dependency>

2、配置

代码语言:javascript
复制
LoadingCache<String, String> localCache = CacheBuilder.newBuilder()
                .concurrencyLevel(50)
                .expireAfterWrite(60, TimeUnit.SECONDS)
                .initialCapacity(10000)
                .maximumSize(50000)
                .recordStats()
                .removalListener(new RemovalListener<String, String>() {
                    @Override
                    public void onRemoval(RemovalNotification<String, String> notification) {
                        //自己处理
                    }
                })
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String cacheKey) {
                        String res = null;
                        //业务处理并设置res
                        return res;
                    }
                });

一般我们通过CacheBuilder设置相关参数,常用参数如下:

concurrencyLevel:并发等级,内部会根据这个值设置多少个hash slot,一个slot可以理解为相同hash key的数组,然后每一项又是一个链表,这就是我们书中学到的hash冲突的拉链法;

expireAfterWrite:写入多久之后过期,即被删除

maximumSize:缓存个数,注意这里是缓存多少个key,而不是缓存占用的字节;

removalListener:删除监听器,可以在被删除的时候做些处理,如更新

build:主要的方法,参数为CacheLoader,这是一个抽象类,必须实现的方法有load,即根据单个key得到缓存内容,还有个loadAll,用于一次取多个key的内容时被用到;

3、使用

使用就比较简单了,还是以上面的例子说明:

代码语言:javascript
复制
String res = localCache.get("hello");

localCache的类型为LoadingCache<String, String>,我们传一个String给它,它返回一个String。

注意,如果Key是一个对象,则必须自己重写hashCode和equals方法,LoadingCache是通过这2个方法计算hash值和判断是否相等的。

三、设计分析

回到前面的几个问题,看Guava里是怎么解决的;

1、数据存储结构

LoadingCache最终是LocalCache来实现的,我们看下它的结构:

它是用一个数组Segment<K, V> segments 来存放缓存的内容的;

即它自己造的轮子,根据key算出hash得到key应该存在哪个segment,这也符合Google的习惯,Google的技术实力雄厚,对于这种很底层的东西它认为自己的代码性能可能更好;

2、限制缓存大小

前面已经说了,是通过限制缓存key的个数,即maximumSize方法来限制,这样也简单

3、更新问题

从接口声明上它已经声明了refresh方法,即支持自己刷新;

4、并发问题

代码语言:javascript
复制
V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
      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);
            }
          }
        }

        // at this point e is either null or expired;
        return lockedGetOrLoad(key, hash, loader);
      } catch (ExecutionException ee) {
      //
      }

可以关注scheduleRefresh和lockedGetOrLoad方法,具体过程是缓存过期的时候是有通过锁和CAS等方式保证只有一个线程来更新,而其它线程在这个时候得到还是旧的值。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员升级之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档