diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/AbstractCache.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/AbstractCache.java index a9d1b60020..9389c6bbfc 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/AbstractCache.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/AbstractCache.java @@ -53,11 +53,6 @@ public abstract class AbstractCache implements Cache { */ protected Map, CacheObj> cacheMap; - /** - * 写的时候每个key一把锁,降低锁的粒度 - */ - protected final Map keyLockMap = new ConcurrentHashMap<>(); - /** * 返回缓存容量,{@code 0}表示无大小限制 */ @@ -139,33 +134,8 @@ public abstract class AbstractCache implements Cache { return missCount.sum(); } - @Override - public V get(final K key, final boolean isUpdateLastAccess, final long timeout, final SerSupplier valueFactory) { - V v = get(key, isUpdateLastAccess); - if (null == v && null != valueFactory) { - //每个key单独获取一把锁,降低锁的粒度提高并发能力,see pr#1385@Github - final Lock keyLock = keyLockMap.computeIfAbsent(key, k -> new ReentrantLock()); - keyLock.lock(); - try { - // 双重检查锁,防止在竞争锁的过程中已经有其它线程写入 - // issue#3686 由于这个方法内的加锁是get独立锁,不和put锁互斥,而put和pruneCache会修改cacheMap,导致在pruneCache过程中get会有并发问题 - // 因此此处需要使用带全局锁的get获取值 - v = get(key, isUpdateLastAccess); - if (null == v) { - // supplier的创建是一个耗时过程,此处创建与全局锁无关,而与key锁相关,这样就保证每个key只创建一个value,且互斥 - v = valueFactory.get(); - put(key, v, timeout); - } - } finally { - keyLock.unlock(); - keyLockMap.remove(key); - } - } - return v; - } - /** - * 获取键对应的{@link CacheObj} + * 获取键对应的{@link CacheObj},不判断是否过期 * * @param key 键,实际使用时会被包装为{@link MutableObj} * @return {@link CacheObj} @@ -174,6 +144,23 @@ public abstract class AbstractCache implements Cache { protected CacheObj getWithoutLock(final K key) { return this.cacheMap.get(MutableObj.of(key)); } + + /** + * 获得CacheObj或清除过期CacheObj,不加锁 + * + * @param key 键 + * @return 值或null + */ + protected CacheObj getOrRemoveExpiredWithoutLock(final K key) { + CacheObj co = getWithoutLock(key); + if (null != co && co.isExpired()) { + //过期移除 + removeWithoutLock(key); + onRemove(co.key, co.obj); + co = null; + } + return co; + } // ---------------------------------------------------------------- get end @Override diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/LockedCache.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/LockedCache.java index c1c73deb96..2a7254a303 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/LockedCache.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/LockedCache.java @@ -18,6 +18,7 @@ package cn.hutool.v7.core.cache.impl; import cn.hutool.v7.core.collection.iter.CopiedIter; import cn.hutool.v7.core.collection.set.SetUtil; +import cn.hutool.v7.core.func.SerSupplier; import cn.hutool.v7.core.lang.mutable.Mutable; import java.io.Serial; @@ -63,6 +64,31 @@ public abstract class LockedCache extends AbstractCache { return getOrRemoveExpired(key, isUpdateLastAccess, true); } + @Override + public V get(final K key, final boolean isUpdateLastAccess, final long timeout, final SerSupplier valueFactory) { + V v = get(key, isUpdateLastAccess); + + // 对象不存在,则加锁创建 + if (null == v && null != valueFactory) { + // 按照pr#1385提议,使用key锁可以避免对象创建等待问题,但是会带来循环锁问题,见:issue#4022 + // 因此此处依旧采用全局锁,在对象创建过程中,全局等待,避免循环锁依赖 + // 这样避免了循环锁,但是会存在一个缺点,即对象创建过程中,其它线程无法获得锁,从而无法使用缓存,因此需要考虑对象创建的耗时问题 + lock.lock(); + try { + // 双重检查锁,防止在竞争锁的过程中已经有其它线程写入 + final CacheObj co = getOrRemoveExpiredWithoutLock(key); + if (null == co) { + // supplier的创建是一个耗时过程,此处创建与全局锁无关,而与key锁相关,这样就保证每个key只创建一个value,且互斥 + v = valueFactory.get(); + put(key, v, timeout); + } + } finally { + lock.unlock(); + } + } + return v; + } + @Override public Iterator> cacheObjIterator() { CopiedIter> copiedIterator; @@ -129,35 +155,30 @@ public abstract class LockedCache extends AbstractCache { /** * 获得值或清除过期值 - * @param key 键 + * + * @param key 键 * @param isUpdateLastAccess 是否更新最后访问时间 - * @param isUpdateCount 是否更新计数器 + * @param isUpdateCount 是否更新计数器 * @return 值或null */ private V getOrRemoveExpired(final K key, final boolean isUpdateLastAccess, final boolean isUpdateCount) { CacheObj co; lock.lock(); try { - co = getWithoutLock(key); - if(null != co && co.isExpired()){ - //过期移除 - removeWithoutLock(key); - onRemove(co.key, co.obj); - co = null; - } + co = getOrRemoveExpiredWithoutLock(key); } finally { lock.unlock(); } // 未命中 if (null == co) { - if(isUpdateCount){ + if (isUpdateCount) { missCount.increment(); } return null; } - if(isUpdateCount){ + if (isUpdateCount) { hitCount.increment(); } return co.get(isUpdateLastAccess);