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类的关键一环,其中二级缓存的设计思想很有研究的价值,特别是一些内部类的设计与使用,都可以给我们之后的编码带来启发。

猜你喜欢

转载自blog.csdn.net/sinat_36945592/article/details/88071228