Guava LocalCache源码分析:LocalCache生成-LocalCache介绍

时间:2024-07-16 17:29:09

LocalCache继承了AbstractMap并实现了ConcurrentMap。
LocalCache基本策略是对Entry分段存储,每个Segment本身都是一个并发可读的哈希表。该映射支持跨不同段的非阻塞读取和并发写入。如果指定了最大大小,则使用页面替换算法对段内的Entry进行替换。

LocalCache实例化

将builder中的属性赋值到LocalCache中

LocalCache(CacheBuilder<? super K, ? super V> builder, @CheckForNull CacheLoader<? super K, V> loader) {
        //从builder和默认(2^16)中选的最小的作为并发级别
        concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS);
        //键值策略
        keyStrength = builder.getKeyStrength();
        valueStrength = builder.getValueStrength();
        //键值比较策略
        keyEquivalence = builder.getKeyEquivalence();
        valueEquivalence = builder.getValueEquivalence();
        //最大容量
        maxWeight = builder.getMaximumWeight();
        //容量计算器
        weigher = builder.getWeigher();
        //访问保留时间
        expireAfterAccessNanos = builder.getExpireAfterAccessNanos();
        //写入保留时间
        expireAfterWriteNanos = builder.getExpireAfterWriteNanos();
        //自动刷新间隔
        refreshNanos = builder.getRefreshNanos();
        //监听Entry被GC回收的监听器
        removalListener = builder.getRemovalListener();
        //removalListener的Entry队列,这里如果removalListener未设置,
        //则用的是discardingQueue,如果已设置则使用的是ConcurrentLinkedQueue
        removalNotificationQueue = (removalListener == NullListener.INSTANCE) ? LocalCache.discardingQueue()  : new ConcurrentLinkedQueue<>();
        //计数器
        ticker = builder.getTicker(recordsTime());
        //实体工厂
        entryFactory = EntryFactory.getFactory(keyStrength, usesAccessEntries(), usesWriteEntries());
        //全局缓存数
        globalStatsCounter = builder.getStatsCounterSupplier().get();
        //默认加载器
        defaultLoader = loader;
        //设置初始容量,在builder的初始容量和2^30选更小的一个
        int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY);
        //如果Map的最大容量>0且weigher是OneWeigher的实例
        if (evictsBySize() && !customWeigher()) {
            //初始容量是当前值与maxWeight之间更小的一个
            initialCapacity = (int) Math.min(initialCapacity, maxWeight);
        }
        ……
}

其中evictsBySize方法和customWeigher方法如下:

    boolean evictsBySize() {
        return maxWeight >= 0;
    }
    boolean customWeigher() {
        return weigher != OneWeigher.INSTANCE;
    }

分段

  1. 计算段个数,如果未指定最大容量时,段个数=超过并发级别的最小2次幂,指定了最大容量时,段个数=超过并发级别和最大容量/20之间较小的值的最小2次幂。
        // 除非指定了maximumSize/Weight(在这种情况下,请确保每个段至少有10个Entry),
        // 否则找到超过并发级别的最小2次幂的段数量,然后对该段中的Entry进行移除,
        // 因为移除是按段而不是全局进行的,因此与最大大小相比,过多的段将导致随机移除行为。
        int segmentShift = 0;
        int segmentCount = 1;
        //未指定最大容量时,segmentCount=超过concurrencyLevel的最小2次幂
        //指定了最大容量时,segmentCount=超过Math.Min(concurrencyLevel,maxWeight/20)的最小2次幂
        while (segmentCount < concurrencyLevel
                && (!evictsBySize() || segmentCount * 20L <= maxWeight)) {
            ++segmentShift;
            segmentCount <<= 1;
        }

        this.segmentShift = 32 - segmentShift;
        segmentMask = segmentCount - 1;
  1. 通过newSegmentArray()创建段
        this.segments = newSegmentArray(segmentCount);
  1. 计算段容量,cache的段容量由两个变量控制,segmentSize为初始容量,maxSegmentWeight为能够扩容的最大容量。
        //段容量=Math.Ceiling(总容量/段数)
        int segmentCapacity = initialCapacity / segmentCount;
        if (segmentCapacity * segmentCount < initialCapacity) {
            ++segmentCapacity;
        }

        //segmentSize=超过segmentCapacity的最小2次幂
        int segmentSize = 1;
        while (segmentSize < segmentCapacity) {
            segmentSize <<= 1;
        }

        //如果设置了Map的最大容量
        if (evictsBySize()) {
            //确保分段最大容量之和=总最大容量
            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 {
            //没有设置最大容量,则每段大小为UNSET_INT
            for (int i = 0; i < this.segments.length; ++i) {
                this.segments[i] =
                        createSegment(segmentSize, UNSET_INT, builder.getStatsCounterSupplier().get());
            }
        }

初始容量计算比较简单,为超过Math.Ceiling(总容量/段数)的最小2次幂。如果设置了Map的最大容量,最大段容量计算如下图所示:
最大段容量计算
假设分段最大容量为25,段数量为7,则通过公式计算得出maxSegmentWeight=4,remainder=4。根据for循环,当下标为4时,maxSegmentWeight=maxSegmentWeight-1,则前4个段最大容量为4,而后三个段的最大容量为3。如此,确保了分段最大容量之和=总最大容量。
当没有设置最大容量,则每段大小为UNSET_INT。