• Java源码阅读------WeakCache


    描述

    根据名字就知道这是一个缓存类,具体点是一个键值对存储的缓存类,至于weak的含义是因为这里的键与值都是弱引用,除此之外,这里所说的缓存是一个二级缓存。第一层是弱引用,第二层是强引用。

    实现

    二级缓存

    private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();
    private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>();
    

    这里的map就是一个二级缓存的实现机制,Object(最左边的)就是key(键),第二个Object为subKey(子键),最后的Supplier< V >接口为值,实际是一个包裹最终是由其中的get方法来获取值。这种设计保证了key为null时也可用。reverseMap用于标注每个Supplier< V >的可用情况,用于缓存的过期处理。

    构造方法

    这里的K,P,V分别是键、参数、值,传入的参数是两个BiFunction接口,主要使用的是其中的apply方法,定义如下:

    R apply(T t, U u);
    

    通过传入的两个参数来获取值,使用这一接口实现了两个简单的工厂,subKeyFactory,valueFactory,这两个工厂分别实现了通过K与P来获取subKey(子键)和通过K,P来获取value值。

    public WeakCache(BiFunction<K, P, ?> subKeyFactory, BiFunction<K, P, V> valueFactory) {
    	this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
    	this.valueFactory = Objects.requireNonNull(valueFactory);
    }
    

    这样我们就可以通过subKeyFactory ,valueFactory 获取对应的子键与值。

    CacheValue

    静态内部类,实际上就是用于存储一个值的对象。
    先看看WeakCache中的Value接口继承自Supplier接口,实际上是为了实现get方法

    private interface Value<V> extends Supplier<V> {}
    
    @FunctionalInterface
    public interface Supplier<T> {
        T get();
    }
    

    构造方法

    private final int hash;
    CacheValue(V value) {
    	super(value);
        this.hash = System.identityHashCode(value); // compare by identity
    }
    

    实现了传入的Value的实例保存,同时通过System.identityHashCode(value);函数获取了该Value的HashCode。

    hashCode与identityHashCode

    在Object类中的hashCode可以获取相应对象的hashCode,而这个identityHashCode也是可以获取对象的hashCode,那么两这有什么不同吗?从源码看两者都是本地方法(native),实际上获取时的结果是与hashCode无异的,但是这里的hashCode指的是原有的Object中的hashCode的方法,如果进行了重写就可能会有不同了,所以为了得到原有的Object中的hashCode的值,identityHashCode会比较方便。

    hashCode

    重写的hashCode方法,直接返回hash值。

    @Override
    public int hashCode() {
    	return hash;
    }
    

    equals

    重写的equals方法,用于判别传入的obj与弱引用中的value是否相同,这里的get()方法就是返回之前传入的弱引用的value。

    @Override
    public boolean equals(Object obj) {
    	V value;
        return obj == this || obj instanceof Value &&
                (value = get()) != null &&  value == ((Value<?>) obj).get(); 
    }
    

    LookupValue

    静态内部类,为了便于对CacheValue中的值进行判断,建立了LookupValue,也实现了Value接口,是CacheValue运算时的替代,实现方式也很相似。

    		private final V value;//存储实际的值
    
            LookupValue(V value) {//构造方法
                this.value = value;
            }
    
            @Override
            public V get() {//Value接口中的get方法,返回value的值
                return value;
            }
    
            @Override
            public int hashCode() {
                return System.identityHashCode(value); //一样的hashCode计算
            }
    
            @Override
            public boolean equals(Object obj) {
                return obj == this ||
                       obj instanceof Value &&
                       this.value == ((Value<?>) obj).get();  // 类似的equals重写
            }
    

    CacheKey

    静态内部类,CacheKey直接继承了使用引用队列的弱引用,来存储键值。

    构造方法

    实现的过程与CacheValue中的类似,只不过这里使用引用队列。

    private final int hash;
    
    private CacheKey(K key, ReferenceQueue<K> refQueue) {
    	super(key, refQueue);
        this.hash = System.identityHashCode(key); 
    }
    

    但是这里有一点注意,这里的构造方法是private的也就是说无法从外界直接调用,那么是如何构建出实例对象的呢?

    valueOf

    这是一个静态方法,传入的是要保存的key与对应的请求队列。

    static <K> Object valueOf(K key, ReferenceQueue<K> refQueue) {
    	return key == null ? NULL_KEY : new CacheKey<>(key, refQueue);
    }
    

    这里的NULL_KEY是一个标识用于标注key为空的情况。

    private static final Object NULL_KEY = new Object();
    

    hashCode与equals

    这里重写了hashCode与equals方法,基本的实现与CacheValue相同,不再赘述。

    @Override
    public int hashCode() {
    	return hash;
    }
    
    @Override
    public boolean equals(Object obj) {
    	K key;
        return obj == this || obj != null &&
               obj.getClass() == this.getClass() && (key = this.get()) != null &&
               key == ((CacheKey<K>) obj).get();
    }
    

    这里的get方法是引用Reference中定义的与之后描述的get方法不同。

    expungeFrom

    通过这个方法可以将含有这个键值的相关缓存清除。

    void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map, ConcurrentMap<?, Boolean> reverseMap) {
    	ConcurrentMap<?, ?> valuesMap = map.remove(this);//直接从二级缓存中清除,并获取第二级的缓存map
        if (valuesMap != null) {//遍历第二级缓存并在reverseMap中清除
        	for (Object cacheValue : valuesMap.values()) {
            	reverseMap.remove(cacheValue);
            }
        }
    }
    

    Factory

    二级缓存的构建过程是通过这个静态内部类实现的,实现了Supplier接口。

    构造方法

    		private final K key;//键
            private final P parameter;//参数
            private final Object subKey;//子键
            private final ConcurrentMap<Object, Supplier<V>> valuesMap;//二级缓存
    
            Factory(K key, P parameter, Object subKey, ConcurrentMap<Object, Supplier<V>> valuesMap) {
                this.key = key;
                this.parameter = parameter;
                this.subKey = subKey;
                this.valuesMap = valuesMap;
            }
    

    简单的数据准备工作。

    get

    这就是构建的具体过程了,实现了Supplier中的get方法,通过同步synchronized来保持线程安全

    		@Override
            public synchronized V get() { // serialize access
                // 这里有一个检查,为啥检查先买个关子,到之后WeakCatch的get方法再做细究
                Supplier<V> supplier = valuesMap.get(subKey);
                if (supplier != this) {
                    return null;
                }
                V value = null;
                try {
                    value = Objects.requireNonNull(valueFactory.apply(key, parameter));
                    //通过key与parameter处理出value
                } finally {
                    if (value == null) { // 失败的处理
                        valuesMap.remove(subKey, this);//移除相应的subKey对应的缓存
                    }
                }
                // 检查value是否完成建立
                assert value != null;
    
                // 包裹一下value建立一个cacheValue
                CacheValue<V> cacheValue = new CacheValue<>(value);
                // 将subKey中的二级缓存中原本的Factory换成包装过的cacheValue,至于为啥一开始二级缓存中会将Factory放进去我们也将其放到WeakCatch的get方法中解释
                if (valuesMap.replace(subKey, this, cacheValue)) {
                    // 加入reverseMap中标记Value可用
                    reverseMap.put(cacheValue, Boolean.TRUE);
                } else {//出错时的异常处理
                    throw new AssertionError("Should not reach here");
                }
                //处理成功后将value返回
                return value;
            }
    

    expungeStaleEntries

    由于二级缓存中的Key使用了弱引用,所以在实际使用时gc的不定期处理会导致部分的缓存失效,通过这个函数就可以实现对失效缓存的清除。

    	private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();
    	private void expungeStaleEntries() {
            CacheKey<K> cacheKey;
            while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {
                cacheKey.expungeFrom(map, reverseMap);
            }
        }
    

    refQueue是弱引用中的引用队列,在创建CacheKey时传入。(建议了解弱引用后继续)。
    当gc处理了部分CacheKey时,refQueue中会有CacheKey的引用,取出来后在调用expungeFrom方法来清除过期的缓存。

    size

    同样的在获取可用的缓存数量时也使用了expungeStaleEntries来先清除过期的缓存。

    	public int size() {
            expungeStaleEntries();
            return reverseMap.size();
        }
    

    containsValue

    判断缓存中是否包含对应的value。

    	public boolean containsValue(V value) {
            Objects.requireNonNull(value);//对值进行判空
            expungeStaleEntries();//清除过期缓存
            return reverseMap.containsKey(new LookupValue<>(value));//使用LookupValue代替CacheValue使用,简化操作。
        }
    

    get

    压轴的高潮来了,这是整个类的灵魂所在,缓存中Value的值是由这个函数来获取的。

    	public V get(K key, P parameter) {
    		//参数判空
            Objects.requireNonNull(parameter);
    		//清除过期缓存
            expungeStaleEntries();
    		//根据key生成相应的cacheKey 
            Object cacheKey = CacheKey.valueOf(key, refQueue);
    
            // 获取二级缓存
            ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
            if (valuesMap == null) {//如果没有对应的二级缓存
                ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());//这里的putIfAbsent与put不同,put会直接替代,putIfAbsent是先判断是否含有值,如果有就返回对应值,如果没有就放入新值并返回null
                //如果之前含有值就使用之前的。
                if (oldValuesMap != null) {
                    valuesMap = oldValuesMap;
                }
            }
            // 通过subKeyFactory的apply方法创建subKey
            Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
            // 通过subKey获取相应的值
            Supplier<V> supplier = valuesMap.get(subKey);
            Factory factory = null;
    
            while (true) {
            	//缓存中有值且不为null就用get方法取出判空返回
                if (supplier != null) {
                    // 根据之前的Factory中的操作,我们知道这里的supplier可能是Factory或CacheValue<V>类型
                    V value = supplier.get();
                    if (value != null) {
                        return value;
                    }
                }
                //缓存没有成功取到或是没有缓存时,使用Factory进行加载。
                if (factory == null) {
                	//创建factory
                    factory = new Factory(key, parameter, subKey, valuesMap);
                }
    			//不含缓存或取值为空的处理
                if (supplier == null) {
                    supplier = valuesMap.putIfAbsent(subKey, factory);
                    if (supplier == null) {
                        //缓存加入成功,将最终的返回值设为factory在下一次循环时返回
                        supplier = factory;
                    }
                } else {//之前含有缓存
                    if (valuesMap.replace(subKey, supplier, factory)) {//用新的factory进行替换
                        //成功替换后就将返回值设为factory在下一次循环时返回
                        supplier = factory;
                    } else {//替换失败则将其原来的值取出
                        supplier = valuesMap.get(subKey);
                    }
                }
            }
        }
    

    说到这里还有一个问题没有解决就是循环轮询的目的是啥,由于在实际使用时可能有多个线程对缓存中的值进行操作,所以使用轮询来不断的进行判断以获取最新的值,这里的get方法可以与factory中的get方法比较来看,就很好理解为啥在factory的get方法中要对取到的supplier进行判断了。

    小结

    WeakCache是实现Proxy类的关键一环,其中二级缓存的设计思想很有研究的价值,特别是一些内部类的设计与使用,都可以给我们之后的编码带来启发。

  • 相关阅读:
    在linux系统上源码安装nginx前的准备
    linux上源码安装ftp
    CentOS-7.2网络配置
    linux安装nginx过程中出现的问题及解决办法
    ubuntu:安装httpd和nginx步骤和常见问题及解决办法
    APP性能(Android手机):帧率FPS
    APP性能(Android手机):APP启动时间
    APP性能(Android手机):流量
    常用网址
    navicat mysql与sqlserver数据互转
  • 原文地址:https://www.cnblogs.com/yanzs/p/13788275.html
Copyright © 2020-2023  润新知