Android LruCache初探

本文深入探讨了Android LruCache的并发访问机制,通过解析其内部数据结构和关键方法,详细解释了如何在多线程环境下安全高效地管理缓存数据。重点介绍了LruCache如何封装LinkedHashMap并加入线程锁操作,确保了缓存数据的一致性和有效性。
摘要由CSDN通过智能技术生成

LinkedHashMap中,我们知道,LinkedHashMap为我们实现特定替换策略的Map Cache预留了接口,即以如下形式重写removeEldestEntry函数:

          private static final int MAX_ENTRIES = 100;
     
          protected boolean removeEldestEntry(Map.Entry eldest) {
             return size() > MAX_ENTRIES;
          }
但是LinkedHashMap有一点不足在于其实现过程中没有考虑过并发访问的问题,即在多线程环境下对LinkedHashMap进行访问并不安全。

1.Andriod LruCache概述

Andriod开发这网站上给出如下描述:

A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.

If your cached values hold resources that need to be explicitly released, override entryRemoved(boolean, K, V, V).

If a cache miss should be computed on demand for the corresponding keys, override create(K). This simplifies the calling code, allowing it to assume a value will always be returned, even when there's a cache miss.

By default, the cache size is measured in the number of entries. Override sizeOf(K, V) to size the cache in different units. 

可以看出在Andriod LruCache中实现中还是给开发这留出了几个灵活的扩展接口,包括 entryRemoved(boolean, K, V, V)用于对特定的Cache元素进行内存空间释放操作  ,  create(K) 用于在访问失效时,为特定的key生成键值的操作,当然这两个函数在源码实现中都是空。

2.底层数据结构支持

我最初以为Andriod LruCache的实现是继承了LinkedHashMap,并重写了removeEldestEntry函数,但是一看源码完全不是这么回事,所以首先看一下LruCache的内部数据域以及其构造函数:
        private final LinkedHashMap<K, V> map;         //LinkedHashMap作为一个成员变量操作

	/** Size of this cache in units. Not necessarily the number of elements. */
	private int size;
	private int maxSize;                           //Cache元素的个数上限

	private int putCount;
	private int createCount;
	private int evictionCount;
	private int hitCount;
	private int missCount;

	/**
	 * @param maxSize for caches that do not override {@link #sizeOf}, this is
	 *     the maximum number of entries in the cache. For all other caches,
	 *     this is the maximum sum of the sizes of the entries in this cache.
	 */
	public LruCache(int maxSize) {
		if (maxSize <= 0) {                //构造函数,初始化LinkedHashMap
			throw new IllegalArgumentException("maxSize <= 0");
		}
		this.maxSize = maxSize;
		this.map = new LinkedHashMap<K, V>(0, 0.75f, true);    //注意参数,true代表这AccessOrder
	}
到这里你会想到,当我们对LruCache进行操作时,都是对其内部封装的LinkedHashMap进行操作。

3.put

put函数内部还是调用了LinkedHashMap的put操作,但是在操作过程中进行了加锁操作,保证同一时刻只能有一个线程进行数据插入操作。
/**
	 * Caches {@code value} for {@code key}. The value is moved to the head of
	 * the queue.
	 *
	 * @return the previous value mapped by {@code key}.
	 */
	public final V put(K key, V value) {
		if (key == null || value == null) {
			throw new NullPointerException("key == null || value == null");
		}

		V previous;
		synchronized (this) {
			putCount++;
			size += safeSizeOf(key, value);
			previous = map.put(key, value);     //调用LinkedHashMap的put操作
			if (previous != null) {
				size -= safeSizeOf(key, previous);
			}
		}

		if (previous != null) {
			entryRemoved(false, key, previous, value);
		}

		trimToSize(maxSize);                         //缓存容量检测,以保证缓存数据量不超过最大容量
		return previous;
	}

4.get

/**
	 * Returns the value for {@code key} if it exists in the cache or can be
	 * created by {@code #create}. If a value was returned, it is moved to the
	 * head of the queue. This returns null if a value is not cached and cannot
	 * be created.
	 */
	public final V get(K key) {
		if (key == null) {
			throw new NullPointerException("key == null");
		}

		V mapValue;
		synchronized (this) {                   //加锁的get操作
			mapValue = map.get(key);        //调用LinkedHashMap的get操作
			if (mapValue != null) {
				hitCount++;
				return mapValue;
			}
			missCount++;
		}

        /*
         * Attempt to create a value. This may take a long time, and the map
         * may be different when create() returns. If a conflicting value was
         * added to the map while create() was working, we leave that value in
         * the map and release the created value.
         */

		V createdValue = create(key);                  //对访问失效的处理,英文注释里说的很清楚,就在于你去怎么处理了
		if (createdValue == null) {
			return null;                           //创建不成功直接返回就可以
		}

		synchronized (this) {                          //加锁处理,保证第一个被创建的value被缓存,从而保证了缓存数据的一致性
			createCount++;     
			mapValue = map.put(key, createdValue); //当前线程创建成功,进行put操作 

			if (mapValue != null) {
				// There was a conflict so undo that last put
				map.put(key, mapValue);        //发现之前已经有线程完成创建操作了,保持原有的缓存value操作
			} else {
				size += safeSizeOf(key, createdValue);
			}
		}

		if (mapValue != null) {                         //执行到这里,证明一定发生了替换操作,并且当前线程是后替换操作
			entryRemoved(false, key, createdValue, mapValue);  //释放当前线程创建的value
			return mapValue;
		} else {
			trimToSize(maxSize);                    //到这里证明当前线程完成了创建操作,并且当前线程创建的value被缓存
			return createdValue;                    //所以需要进行缓存容量检测
		}
	}

5.缓存容量控制trimToSize

	/**
	 * @param maxSize the maximum size of the cache before returning. May be -1
	 *     to evict even 0-sized elements.
	 */
	private void trimToSize(int maxSize) {
		while (true) {
			K key;
			V value;
			synchronized (this) {  //加锁进行缓存元素的删除操作
				if (size < 0 || (map.isEmpty() && size != 0)) {
					throw new IllegalStateException(getClass().getName()
							+ ".sizeOf() is reporting inconsistent results!");
				}

				if (size <= maxSize || map.isEmpty()) {
					break;
				}

				Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
				key = toEvict.getKey();
				value = toEvict.getValue();
				map.remove(key);
				size -= safeSizeOf(key, value);
				evictionCount++;
			}

			entryRemoved(true, key, value, null);  //本线程负责释放已存在元素的空间
		}
	}

5.小结

Android的LruCache的实现是对LinkedHashMap进行了一层封装,并在其中加入了支持多线程访问的线程锁操作,因此LruCache时支持并发访问的。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值