首页

解读Hutool源码关于定义缓存工具类CacheUtil实现FIFO、LFU、LRU、弱引用、定时缓存代码实现

标签:先进先出缓存FIFO,first in first out,最少使用率缓存,LFUCache,最近最久未使用缓存,LRUCache,定时缓存TimedCache     发布时间:2021-05-17   

一、前言

关于Hutool定义cn.hutool.cache.Cache缓存接口标准,并定义常用cn.hutool.cache.CacheUtil缓存工具类,实现FIFO(first in first out) 先进先出缓存、LFU(least frequently used) 最少使用率缓存、LRU (least recently used)最近最久未使用缓存、TimedCache定时缓存、WeakCache弱引用缓存及NoCache无缓存各个不同场景缓存需求实现。

--FIFO(first in first out) 先进先出缓存@b@cn.hutool.cache.impl.FIFOCache;@b@@b@--LFU(least frequently used) 最少使用率缓存.@b@cn.hutool.cache.impl.LFUCache;@b@@b@--LRU (least recently used)最近最久未使用缓存.@b@cn.hutool.cache.impl.LRUCache;@b@@b@--无缓存实现.@b@cn.hutool.cache.impl.NoCache;@b@@b@--定时缓存.@b@cn.hutool.cache.impl.TimedCache;@b@@b@--弱引用缓存.@b@cn.hutool.cache.impl.WeakCache;

二、源码示例

1、定义缓存Cache接口

package cn.hutool.cache;@b@@b@import cn.hutool.cache.impl.CacheObj;@b@import cn.hutool.core.lang.func.Func0;@b@@b@import java.io.Serializable;@b@import java.util.Iterator;@b@@b@/**@b@ * 缓存接口@b@ *@b@ * @param <K> 键类型@b@ * @param <V> 值类型@b@ * @author Looly, jodd@b@ */@b@public interface Cache<K, V> extends Iterable<V>, Serializable {@b@@b@	/**@b@	 * 返回缓存容量,{@code 0}表示无大小限制@b@	 *@b@	 * @return 返回缓存容量,{@code 0}表示无大小限制@b@	 */@b@	int capacity();@b@@b@	/**@b@	 * 缓存失效时长, {@code 0} 表示没有设置,单位毫秒@b@	 *@b@	 * @return 缓存失效时长, {@code 0} 表示没有设置,单位毫秒@b@	 */@b@	long timeout();@b@@b@	/**@b@	 * 将对象加入到缓存,使用默认失效时长@b@	 *@b@	 * @param key    键@b@	 * @param object 缓存的对象@b@	 * @see Cache#put(Object, Object, long)@b@	 */@b@	void put(K key, V object);@b@@b@	/**@b@	 * 将对象加入到缓存,使用指定失效时长<br>@b@	 * 如果缓存空间满了,{@link #prune()} 将被调用以获得空间来存放新对象@b@	 *@b@	 * @param key     键@b@	 * @param object  缓存的对象@b@	 * @param timeout 失效时长,单位毫秒@b@	 */@b@	void put(K key, V object, long timeout);@b@@b@	/**@b@	 * 从缓存中获得对象,当对象不在缓存中或已经过期返回{@code null}@b@	 * <p>@b@	 * 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回{@code null},否则返回值。@b@	 * <p>@b@	 * 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。@b@	 *@b@	 * @param key 键@b@	 * @return 键对应的对象@b@	 * @see #get(Object, boolean)@b@	 */@b@	default V get(K key) {@b@		return get(key, true);@b@	}@b@@b@	/**@b@	 * 从缓存中获得对象,当对象不在缓存中或已经过期返回Func0回调产生的对象@b@	 * <p>@b@	 * 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回{@code null},否则返回值。@b@	 * <p>@b@	 * 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。@b@	 *@b@	 * @param key      键@b@	 * @param supplier 如果不存在回调方法,用于生产值对象@b@	 * @return 值对象@b@	 */@b@	default V get(K key, Func0<V> supplier) {@b@		return get(key, true, supplier);@b@	}@b@@b@	/**@b@	 * 从缓存中获得对象,当对象不在缓存中或已经过期返回Func0回调产生的对象@b@	 * <p>@b@	 * 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回{@code null},否则返回值。@b@	 * <p>@b@	 * 每次调用此方法会可选是否刷新最后访问时间,{@code true}表示会重新计算超时时间。@b@	 *@b@	 * @param key                键@b@	 * @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。@b@	 * @param supplier           如果不存在回调方法,用于生产值对象@b@	 * @return 值对象@b@	 */@b@	V get(K key, boolean isUpdateLastAccess, Func0<V> supplier);@b@@b@	/**@b@	 * 从缓存中获得对象,当对象不在缓存中或已经过期返回{@code null}@b@	 * <p>@b@	 * 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回{@code null},否则返回值。@b@	 * <p>@b@	 * 每次调用此方法会可选是否刷新最后访问时间,{@code true}表示会重新计算超时时间。@b@	 *@b@	 * @param key                键@b@	 * @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。@b@	 * @return 键对应的对象@b@	 */@b@	V get(K key, boolean isUpdateLastAccess);@b@@b@	/**@b@	 * 返回包含键和值得迭代器@b@	 *@b@	 * @return 缓存对象迭代器@b@	 * @since 4.0.10@b@	 */@b@	Iterator<CacheObj<K, V>> cacheObjIterator();@b@@b@	/**@b@	 * 从缓存中清理过期对象,清理策略取决于具体实现@b@	 *@b@	 * @return 清理的缓存对象个数@b@	 */@b@	int prune();@b@@b@	/**@b@	 * 缓存是否已满,仅用于有空间限制的缓存对象@b@	 *@b@	 * @return 缓存是否已满,仅用于有空间限制的缓存对象@b@	 */@b@	boolean isFull();@b@@b@	/**@b@	 * 从缓存中移除对象@b@	 *@b@	 * @param key 键@b@	 */@b@	void remove(K key);@b@@b@	/**@b@	 * 清空缓存@b@	 */@b@	void clear();@b@@b@	/**@b@	 * 缓存的对象数量@b@	 *@b@	 * @return 缓存的对象数量@b@	 */@b@	int size();@b@@b@	/**@b@	 * 缓存是否为空@b@	 *@b@	 * @return 缓存是否为空@b@	 */@b@	boolean isEmpty();@b@@b@	/**@b@	 * 是否包含key@b@	 *@b@	 * @param key KEY@b@	 * @return 是否包含key@b@	 */@b@	boolean containsKey(K key);@b@@b@	/**@b@	 * 设置监听@b@	 *@b@	 * @param listener 监听@b@	 * @return this@b@	 * @since 5.5.2@b@	 */@b@	default Cache<K, V> setListener(CacheListener<K, V> listener){@b@		return this;@b@	}@b@}

2、定义CacheUtil实现不同缓存代码

package cn.hutool.cache;@b@@b@import cn.hutool.cache.impl.FIFOCache;@b@import cn.hutool.cache.impl.LFUCache;@b@import cn.hutool.cache.impl.LRUCache;@b@import cn.hutool.cache.impl.NoCache;@b@import cn.hutool.cache.impl.TimedCache;@b@import cn.hutool.cache.impl.WeakCache;@b@@b@/**@b@ * 缓存工具类@b@ * @author Looly@b@ *@since 3.0.1@b@ */@b@public class CacheUtil {@b@@b@	/**@b@	 * 创建FIFO(first in first out) 先进先出缓存.@b@	 *@b@	 * @param <K> Key类型@b@	 * @param <V> Value类型@b@	 * @param capacity 容量@b@	 * @param timeout 过期时长,单位:毫秒@b@	 * @return {@link FIFOCache}@b@	 */@b@	public static <K, V> FIFOCache<K, V> newFIFOCache(int capacity, long timeout){@b@		return new FIFOCache<>(capacity, timeout);@b@	}@b@@b@	/**@b@	 * 创建FIFO(first in first out) 先进先出缓存.@b@	 *@b@	 * @param <K> Key类型@b@	 * @param <V> Value类型@b@	 * @param capacity 容量@b@	 * @return {@link FIFOCache}@b@	 */@b@	public static <K, V> FIFOCache<K, V> newFIFOCache(int capacity){@b@		return new FIFOCache<>(capacity);@b@	}@b@@b@	/**@b@	 * 创建LFU(least frequently used) 最少使用率缓存.@b@	 *@b@	 * @param <K> Key类型@b@	 * @param <V> Value类型@b@	 * @param capacity 容量@b@	 * @param timeout 过期时长,单位:毫秒@b@	 * @return {@link LFUCache}@b@	 */@b@	public static <K, V> LFUCache<K, V> newLFUCache(int capacity, long timeout){@b@		return new LFUCache<>(capacity, timeout);@b@	}@b@@b@	/**@b@	 * 创建LFU(least frequently used) 最少使用率缓存.@b@	 *@b@	 * @param <K> Key类型@b@	 * @param <V> Value类型@b@	 * @param capacity 容量@b@	 * @return {@link LFUCache}@b@	 */@b@	public static <K, V> LFUCache<K, V> newLFUCache(int capacity){@b@		return new LFUCache<>(capacity);@b@	}@b@@b@@b@	/**@b@	 * 创建LRU (least recently used)最近最久未使用缓存.@b@	 *@b@	 * @param <K> Key类型@b@	 * @param <V> Value类型@b@	 * @param capacity 容量@b@	 * @param timeout 过期时长,单位:毫秒@b@	 * @return {@link LRUCache}@b@	 */@b@	public static <K, V> LRUCache<K, V> newLRUCache(int capacity, long timeout){@b@		return new LRUCache<>(capacity, timeout);@b@	}@b@@b@	/**@b@	 * 创建LRU (least recently used)最近最久未使用缓存.@b@	 *@b@	 * @param <K> Key类型@b@	 * @param <V> Value类型@b@	 * @param capacity 容量@b@	 * @return {@link LRUCache}@b@	 */@b@	public static <K, V> LRUCache<K, V> newLRUCache(int capacity){@b@		return new LRUCache<>(capacity);@b@	}@b@@b@	/**@b@	 * 创建定时缓存.@b@	 *@b@	 * @param <K> Key类型@b@	 * @param <V> Value类型@b@	 * @param timeout 过期时长,单位:毫秒@b@	 * @return {@link TimedCache}@b@	 */@b@	public static <K, V> TimedCache<K, V> newTimedCache(long timeout){@b@		return new TimedCache<>(timeout);@b@	}@b@@b@	/**@b@	 * 创建弱引用缓存.@b@	 *@b@	 * @param <K> Key类型@b@	 * @param <V> Value类型@b@	 * @param timeout 过期时长,单位:毫秒@b@	 * @return {@link WeakCache}@b@	 * @since 3.0.7@b@	 */@b@	public static <K, V> WeakCache<K, V> newWeakCache(long timeout){@b@		return new WeakCache<>(timeout);@b@	}@b@@b@	/**@b@	 * 创建无缓存实现.@b@	 *@b@	 * @param <K> Key类型@b@	 * @param <V> Value类型@b@	 * @return {@link NoCache}@b@	 */@b@	public static <K, V> NoCache<K, V> newNoCache(){@b@		return new NoCache<>();@b@	}@b@}

3、公共抽象缓存实现类AbstractCache

package cn.hutool.cache.impl;@b@@b@import cn.hutool.cache.Cache;@b@import cn.hutool.cache.CacheListener;@b@import cn.hutool.core.collection.CopiedIter;@b@import cn.hutool.core.lang.func.Func0;@b@@b@import java.util.Iterator;@b@import java.util.Map;@b@import java.util.Set;@b@import java.util.concurrent.ConcurrentHashMap;@b@import java.util.concurrent.atomic.LongAdder;@b@import java.util.concurrent.locks.Lock;@b@import java.util.concurrent.locks.ReentrantLock;@b@import java.util.concurrent.locks.StampedLock;@b@@b@/**@b@ * 超时和限制大小的缓存的默认实现<br>@b@ * 继承此抽象缓存需要:<br>@b@ * <ul>@b@ * <li>创建一个新的Map</li>@b@ * <li>实现 {@code prune} 策略</li>@b@ * </ul>@b@ *@b@ * @param <K> 键类型@b@ * @param <V> 值类型@b@ * @author Looly, jodd@b@ */@b@public abstract class AbstractCache<K, V> implements Cache<K, V> {@b@	private static final long serialVersionUID = 1L;@b@@b@	protected Map<K, CacheObj<K, V>> cacheMap;@b@@b@	// 乐观锁,此处使用乐观锁解决读多写少的场景@b@	// get时乐观读,再检查是否修改,修改则转入悲观读重新读一遍,可以有效解决在写时阻塞大量读操作的情况。@b@	// see: https://www.cnblogs.com/jiagoushijuzi/p/13721319.html@b@	private final StampedLock lock = new StampedLock();@b@@b@	/**@b@	 * 写的时候每个key一把锁,降低锁的粒度@b@	 */@b@	protected final Map<K, Lock> keyLockMap = new ConcurrentHashMap<>();@b@@b@	/**@b@	 * 返回缓存容量,{@code 0}表示无大小限制@b@	 */@b@	protected int capacity;@b@	/**@b@	 * 缓存失效时长, {@code 0} 表示无限制,单位毫秒@b@	 */@b@	protected long timeout;@b@@b@	/**@b@	 * 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。@b@	 */@b@	protected boolean existCustomTimeout;@b@@b@	/**@b@	 * 命中数,即命中缓存计数@b@	 */@b@	protected LongAdder hitCount = new LongAdder();@b@	/**@b@	 * 丢失数,即未命中缓存计数@b@	 */@b@	protected LongAdder missCount = new LongAdder();@b@@b@	/**@b@	 * 缓存监听@b@	 */@b@	protected CacheListener<K, V> listener;@b@@b@	// ---------------------------------------------------------------- put start@b@	@Override@b@	public void put(K key, V object) {@b@		put(key, object, timeout);@b@	}@b@@b@	@Override@b@	public void put(K key, V object, long timeout) {@b@		final long stamp = lock.writeLock();@b@		try {@b@			putWithoutLock(key, object, timeout);@b@		} finally {@b@			lock.unlockWrite(stamp);@b@		}@b@	}@b@@b@	/**@b@	 * 加入元素,无锁@b@	 *@b@	 * @param key     键@b@	 * @param object  值@b@	 * @param timeout 超时时长@b@	 * @since 4.5.16@b@	 */@b@	private void putWithoutLock(K key, V object, long timeout) {@b@		CacheObj<K, V> co = new CacheObj<>(key, object, timeout);@b@		if (timeout != 0) {@b@			existCustomTimeout = true;@b@		}@b@		if (isFull()) {@b@			pruneCache();@b@		}@b@		cacheMap.put(key, co);@b@	}@b@	// ---------------------------------------------------------------- put end@b@@b@	// ---------------------------------------------------------------- get start@b@	@Override@b@	public boolean containsKey(K key) {@b@		final long stamp = lock.readLock();@b@		try {@b@			// 不存在或已移除@b@			final CacheObj<K, V> co = cacheMap.get(key);@b@			if (co == null) {@b@				return false;@b@			}@b@@b@			if (false == co.isExpired()) {@b@				// 命中@b@				return true;@b@			}@b@		} finally {@b@			lock.unlockRead(stamp);@b@		}@b@@b@		// 过期@b@		remove(key, true);@b@		return false;@b@	}@b@@b@	/**@b@	 * @return 命中数@b@	 */@b@	public long getHitCount() {@b@		return hitCount.sum();@b@	}@b@@b@	/**@b@	 * @return 丢失数@b@	 */@b@	public long getMissCount() {@b@		return missCount.sum();@b@	}@b@@b@	@Override@b@	public V get(K key, boolean isUpdateLastAccess, Func0<V> supplier) {@b@		V v = get(key, isUpdateLastAccess);@b@		if (null == v && null != supplier) {@b@			//每个key单独获取一把锁,降低锁的粒度提高并发能力,see pr#1385@Github@b@			final Lock keyLock = keyLockMap.computeIfAbsent(key, k -> new ReentrantLock());@b@			keyLock.lock();@b@			try {@b@				// 双重检查锁,防止在竞争锁的过程中已经有其它线程写入@b@				final CacheObj<K, V> co = cacheMap.get(key);@b@				if (null == co || co.isExpired()) {@b@					try {@b@						v = supplier.call();@b@					} catch (Exception e) {@b@						throw new RuntimeException(e);@b@					}@b@					put(key, v, this.timeout);@b@				} else {@b@					v = co.get(isUpdateLastAccess);@b@				}@b@			} finally {@b@				keyLock.unlock();@b@				keyLockMap.remove(key);@b@			}@b@		}@b@		return v;@b@	}@b@@b@	@Override@b@	public V get(K key, boolean isUpdateLastAccess) {@b@		// 尝试读取缓存,使用乐观读锁@b@		long stamp = lock.tryOptimisticRead();@b@		CacheObj<K, V> co = cacheMap.get(key);@b@		if(false == lock.validate(stamp)){@b@			// 有写线程修改了此对象,悲观读@b@			stamp = lock.readLock();@b@			try {@b@				co = cacheMap.get(key);@b@			} finally {@b@				lock.unlockRead(stamp);@b@			}@b@		}@b@@b@		// 未命中@b@		if (null == co) {@b@			missCount.increment();@b@			return null;@b@		} else if (false == co.isExpired()) {@b@			hitCount.increment();@b@			return co.get(isUpdateLastAccess);@b@		}@b@@b@		// 过期,既不算命中也不算非命中@b@		remove(key, true);@b@		return null;@b@	}@b@@b@	// ---------------------------------------------------------------- get end@b@@b@	@Override@b@	public Iterator<V> iterator() {@b@		CacheObjIterator<K, V> copiedIterator = (CacheObjIterator<K, V>) this.cacheObjIterator();@b@		return new CacheValuesIterator<>(copiedIterator);@b@	}@b@@b@	@Override@b@	public Iterator<CacheObj<K, V>> cacheObjIterator() {@b@		CopiedIter<CacheObj<K, V>> copiedIterator;@b@		final long stamp = lock.readLock();@b@		try {@b@			copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator());@b@		} finally {@b@			lock.unlockRead(stamp);@b@		}@b@		return new CacheObjIterator<>(copiedIterator);@b@	}@b@@b@	// ---------------------------------------------------------------- prune start@b@@b@	/**@b@	 * 清理实现<br>@b@	 * 子类实现此方法时无需加锁@b@	 *@b@	 * @return 清理数@b@	 */@b@	protected abstract int pruneCache();@b@@b@	@Override@b@	public final int prune() {@b@		final long stamp = lock.writeLock();@b@		try {@b@			return pruneCache();@b@		} finally {@b@			lock.unlockWrite(stamp);@b@		}@b@	}@b@	// ---------------------------------------------------------------- prune end@b@@b@	// ---------------------------------------------------------------- common start@b@	@Override@b@	public int capacity() {@b@		return capacity;@b@	}@b@@b@	/**@b@	 * @return 默认缓存失效时长。<br>@b@	 * 每个对象可以单独设置失效时长@b@	 */@b@	@Override@b@	public long timeout() {@b@		return timeout;@b@	}@b@@b@	/**@b@	 * 只有设置公共缓存失效时长或每个对象单独的失效时长时清理可用@b@	 *@b@	 * @return 过期对象清理是否可用,内部使用@b@	 */@b@	protected boolean isPruneExpiredActive() {@b@		return (timeout != 0) || existCustomTimeout;@b@	}@b@@b@	@Override@b@	public boolean isFull() {@b@		return (capacity > 0) && (cacheMap.size() >= capacity);@b@	}@b@@b@	@Override@b@	public void remove(K key) {@b@		remove(key, false);@b@	}@b@@b@	@Override@b@	public void clear() {@b@		final long stamp = lock.writeLock();@b@		try {@b@			cacheMap.clear();@b@		} finally {@b@			lock.unlockWrite(stamp);@b@		}@b@	}@b@@b@	@Override@b@	public int size() {@b@		return cacheMap.size();@b@	}@b@@b@	@Override@b@	public boolean isEmpty() {@b@		return cacheMap.isEmpty();@b@	}@b@@b@	@Override@b@	public String toString() {@b@		return this.cacheMap.toString();@b@	}@b@	// ---------------------------------------------------------------- common end@b@@b@	/**@b@	 * 设置监听@b@	 *@b@	 * @param listener 监听@b@	 * @return this@b@	 * @since 5.5.2@b@	 */@b@	@Override@b@	public AbstractCache<K, V> setListener(CacheListener<K, V> listener) {@b@		this.listener = listener;@b@		return this;@b@	}@b@@b@	/**@b@	 * 返回所有键@b@	 *@b@	 * @return 所有键@b@	 * @since 5.5.9@b@	 */@b@	public Set<K> keySet(){@b@		return this.cacheMap.keySet();@b@	}@b@@b@	/**@b@	 * 对象移除回调。默认无动作<br>@b@	 * 子类可重写此方法用于监听移除事件,如果重写,listener将无效@b@	 *@b@	 * @param key          键@b@	 * @param cachedObject 被缓存的对象@b@	 */@b@	protected void onRemove(K key, V cachedObject) {@b@		final CacheListener<K, V> listener = this.listener;@b@		if (null != listener) {@b@			listener.onRemove(key, cachedObject);@b@		}@b@	}@b@@b@	/**@b@	 * 移除key对应的对象@b@	 *@b@	 * @param key           键@b@	 * @param withMissCount 是否计数丢失数@b@	 */@b@	private void remove(K key, boolean withMissCount) {@b@		final long stamp = lock.writeLock();@b@		CacheObj<K, V> co;@b@		try {@b@			co = removeWithoutLock(key, withMissCount);@b@		} finally {@b@			lock.unlockWrite(stamp);@b@		}@b@		if (null != co) {@b@			onRemove(co.key, co.obj);@b@		}@b@	}@b@@b@	/**@b@	 * 移除key对应的对象,不加锁@b@	 *@b@	 * @param key           键@b@	 * @param withMissCount 是否计数丢失数@b@	 * @return 移除的对象,无返回null@b@	 */@b@	private CacheObj<K, V> removeWithoutLock(K key, boolean withMissCount) {@b@		final CacheObj<K, V> co = cacheMap.remove(key);@b@		if (withMissCount) {@b@			// 在丢失计数有效的情况下,移除一般为get时的超时操作,此处应该丢失数+1@b@			this.missCount.increment();@b@		}@b@		return co;@b@	}@b@}