• Google guava cache源码解析1--构建缓存器(2)


    此文已由作者赵计刚授权网易云社区发布。

    欢迎访问网易云社区,了解更多网易技术产品运营经验。


    CacheBuilder-->maximumSize(long size)

        /**
         * 指定cache中最多能存放的entry(key-value)个数maximumSize
         * 注意:
         * 1、在entry个数还未达到这个指定个数maximumSize的时候,可能就会发生缓存回收
         * 上边这种情况发生在cache size接近指定个数maximumSize,
         * cache就会回收那些很少会再被用到的缓存(这些缓存会使最近没有被用到或很少用到的),其实说白了就是LRU算法回收缓存
         * 2、maximumSize与maximumWeight不能一起使用,其实后者也很少会使用
         */
        public CacheBuilder<K, V> maximumSize(long size) {
            /* 检查maximumSize是否已经被设置过了 */
            checkState(this.maximumSize == UNSET_INT,
                       "maximum size was already set to %s", 
                       this.maximumSize);
            /* 检查maximumWeight是否已经被设置过了(这就是上边说的第二条)*/
            checkState(this.maximumWeight == UNSET_INT,
                       "maximum weight was already set to %s", 
                       this.maximumWeight);
            /* 这是与maximumWeight配合的一个属性 */
            checkState(this.weigher == null,
                       "maximum size can not be combined with weigher");
            /* 检查设置的maximumSize是不是>=0,通常不会设置为0,否则不会起到缓存作用 */
            checkArgument(size >= 0, "maximum size must not be negative");
            this.maximumSize = size;
            return this;
        }

    注意:

    • 设置整个cache(而非每个Segment)中最多可存放的entry的个数

    CacheBuilder-->build(CacheLoader<? super K1, V1> loader)

        /**
         * 建立一个cache,该缓存器通过使用传入的CacheLoader,
         * 既可以获取已给定key的value,也能够自动的计算和获取缓存(这说的就是get(Object key)的三步原子操作)
         * 当然,这里是线程安全的,线程安全的运行方式与ConcurrentHashMap一致
         */
        public <K1 extends K, V1 extends V> LoadingCache<K1, V1> build(CacheLoader<? super K1, V1> loader) {
            checkWeightWithWeigher();
            return new LocalCache.LocalLoadingCache<K1, V1>(this, loader);
        }

    注意:

    • 要看懂该方法,需要了解一些泛型方法的使用方式与泛型限界

    • 该方法的返回值是一个LoadingCache接口的实现类LocalLoadingCache实例

    • 在build方法需要传入一个CacheLoader的实例,实际使用中使用了匿名内部类来实现的,源码的话,就是一个无参构造器,什么也没做,传入CacheLoader实例的意义就是"类结构"部分所说的load()方法

     在上边调用build时,整个代码的执行权其实就交给了LocalCache.

     

    3.2、LocalCache

    LocalLoadingCahe构造器

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

    说明:在该内部类的无参构造器的调用中,

    1)首先要保证传入的CacheLoader实例非空,

    2)其次创建了一个LocalCache的实例出来,

    3)最后调用父类LocalManualCache的私有构造器将第二步创建出来的LocalCache实例赋给LocalCache的类变量,完成初始化。

    这里最重要的就是第二步,下面着重讲第二步:

    LocalCache的一些属性

        /** 最大容量(2的30次方),即最多可存放2的30次方个entry(key-value) */
        static final int MAXIMUM_CAPACITY = 1 << 30;
    
        /** 最多多少个Segment(2的16次方)*/
        static final int MAX_SEGMENTS = 1 << 16;
    
        /** 用于选择Segment */
        final int segmentMask;
    
        /** 用于选择Segment,尽量将hash打散 */
        final int segmentShift;
    
        /** 底层数据结构,就是一个Segment数组,而每一个Segment就是一个hashtable */
        final Segment<K, V>[] segments;
    
        /** 
         * 并发水平,这是一个用于计算Segment个数的一个数,
         * Segment个数是一个刚刚大于或等于concurrencyLevel的数
         */
        final int concurrencyLevel;
    
        /** 键的引用类型(strong、weak、soft) */
        final Strength keyStrength;
    
        /** 值的引用类型(strong、weak、soft) */
        final Strength valueStrength;
    
        /** The maximum weight of this map. UNSET_INT if there is no maximum. 
         * 如果没有设置,就是-1
         */
        final long maxWeight;
    
        final long expireAfterAccessNanos;
    
        final long expireAfterWriteNanos;
    
        /** Factory used to create new entries. */
        final EntryFactory entryFactory;
    
        /** 默认的缓存加载器,用于做一些缓存加载操作(其实就是load),实现三步原子操作*/
        @Nullable
        final CacheLoader<? super K, V> defaultLoader;
    
        /** 默认的缓存加载器,用于做一些缓存加载操作(其实就是load),实现三步原子操作*/
        @Nullable
        final CacheLoader<? super K, V> defaultLoader;

    说明:关于这些属性的含义,看注释+CacheBuilder部分的属性注释+ConcurrentHashMap的属性注释

    LocalCache-->LocalCache(CacheBuilder, CacheLoader)

        /**
         * 创建一个新的、空的map(并且指定策略、初始化容量和并发水平)
         */
        LocalCache(CacheBuilder<? super K, ? super V> builder,
                   @Nullable CacheLoader<? super K, V> loader) {
            /*
             * 默认并发水平是4,即四个Segment(但要注意concurrencyLevel不一定等于Segment个数)
             * Segment个数:一个刚刚大于或等于concurrencyLevel且是2的几次方的一个数
             */
            concurrencyLevel = Math
                    .min(builder.getConcurrencyLevel(), MAX_SEGMENTS);
    
            keyStrength = builder.getKeyStrength();//默认为Strong,即强引用
            valueStrength = builder.getValueStrength();//默认为Strong,即强引用
    
            // 缓存超时(时间起点:entry的创建或替换(即修改))
            expireAfterWriteNanos = builder.getExpireAfterWriteNanos();
            // 缓存超时(时间起点:entry的创建或替换(即修改)或最后一次访问)
            expireAfterAccessNanos = builder.getExpireAfterAccessNanos();
            //创建entry的工厂
            entryFactory = EntryFactory.getFactory(keyStrength,
                                                      usesAccessEntries(), 
                                                      usesWriteEntries());
            //默认的缓存加载器
            defaultLoader = loader;
    
            // 初始化容量为16,整个cache可以放16个缓存entry
            int initialCapacity = Math.min(builder.getInitialCapacity(),
                                           MAXIMUM_CAPACITY);
    
            int segmentShift = 0;
            int segmentCount = 1;
            //循环条件的&&后边的内容是关于weight的,由于没有设置maxWeight,所以其值为-1-->evictsBySize()返回false
            while (segmentCount < concurrencyLevel
                    && (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
                ++segmentShift;
                segmentCount <<= 1;//找一个刚刚大于或等于concurrencyLevel的Segment数
            }
            this.segmentShift = 32 - segmentShift;
            segmentMask = segmentCount - 1;
    
            this.segments = newSegmentArray(segmentCount);//创建指定大小的数组
    
            int segmentCapacity = initialCapacity / segmentCount;//计算每一个Segment中的容量的值,刚刚大于等于initialCapacity/segmentCount
            if (segmentCapacity * segmentCount < initialCapacity) {
                ++segmentCapacity;
            }
    
            int segmentSize = 1;//每一个Segment的容量
            while (segmentSize < segmentCapacity) {
                segmentSize <<= 1;//刚刚>=segmentCapacity&&是2的几次方的数
            }
    
            if (evictsBySize()) {//由于没有设置maxWeight,所以其值为-1-->evictsBySize()返回false
                // Ensure sum of segment max weights = overall max weights
                long maxSegmentWeight = maxWeight / segmentCount + 1;
                long remainder = maxWeight % segmentCount;
                for (int i = 0; i < this.segments.length; ++i) {
                    if (i == remainder) {
                        maxSegmentWeight--;
                    }
                    this.segments[i] = createSegment(segmentSize, 
                                                     maxSegmentWeight,
                                                     builder.getStatsCounterSupplier().get());
                }
            } else {
                for (int i = 0; i < this.segments.length; ++i) {
                    this.segments[i] = createSegment(segmentSize, 
                                                     UNSET_INT,
                                                     builder.getStatsCounterSupplier().get());
                }
            }
        }

    说明:这里的代码就是整个LocalCache实例的创建过程,非常重要!!!


    免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐

    更多网易技术、产品、运营经验分享请点击


    相关文章:
    【推荐】 Spring Boot + Mybatis 多数据源配置实现读写分离

  • 相关阅读:
    ubuntu下如何批量修改文件后缀名
    vanilla
    Ubuntu apt-get 彻底卸载软件包
    Kendall Rank(肯德尔等级)相关系数
    图像质量评估(IQA)
    conda常用命令
    在ubuntu中搜索文件或文件夹的方法
    libstdc++.so.6: version `GLIBCXX_3.4.21' not found
    迅雷磁力链接转BT种子工具
    springboot 集成mybatis plus3
  • 原文地址:https://www.cnblogs.com/zyfd/p/10138596.html
Copyright © 2020-2023  润新知