remove key lock

This commit is contained in:
Looly
2025-08-25 09:15:36 +08:00
parent fc72a84d07
commit 690dd00b63
2 changed files with 50 additions and 42 deletions

View File

@@ -53,11 +53,6 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
*/
protected Map<Mutable<K>, CacheObj<K, V>> cacheMap;
/**
* 写的时候每个key一把锁降低锁的粒度
*/
protected final Map<K, Lock> keyLockMap = new ConcurrentHashMap<>();
/**
* 返回缓存容量,{@code 0}表示无大小限制
*/
@@ -139,33 +134,8 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
return missCount.sum();
}
@Override
public V get(final K key, final boolean isUpdateLastAccess, final long timeout, final SerSupplier<V> 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<K, V> implements Cache<K, V> {
protected CacheObj<K, V> getWithoutLock(final K key) {
return this.cacheMap.get(MutableObj.of(key));
}
/**
* 获得CacheObj或清除过期CacheObj不加锁
*
* @param key 键
* @return 值或null
*/
protected CacheObj<K, V> getOrRemoveExpiredWithoutLock(final K key) {
CacheObj<K, V> co = getWithoutLock(key);
if (null != co && co.isExpired()) {
//过期移除
removeWithoutLock(key);
onRemove(co.key, co.obj);
co = null;
}
return co;
}
// ---------------------------------------------------------------- get end
@Override

View File

@@ -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<K, V> extends AbstractCache<K, V> {
return getOrRemoveExpired(key, isUpdateLastAccess, true);
}
@Override
public V get(final K key, final boolean isUpdateLastAccess, final long timeout, final SerSupplier<V> valueFactory) {
V v = get(key, isUpdateLastAccess);
// 对象不存在,则加锁创建
if (null == v && null != valueFactory) {
// 按照pr#1385提议使用key锁可以避免对象创建等待问题但是会带来循环锁问题issue#4022
// 因此此处依旧采用全局锁,在对象创建过程中,全局等待,避免循环锁依赖
// 这样避免了循环锁,但是会存在一个缺点,即对象创建过程中,其它线程无法获得锁,从而无法使用缓存,因此需要考虑对象创建的耗时问题
lock.lock();
try {
// 双重检查锁,防止在竞争锁的过程中已经有其它线程写入
final CacheObj<K, V> 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<CacheObj<K, V>> cacheObjIterator() {
CopiedIter<CacheObj<K, V>> copiedIterator;
@@ -129,35 +155,30 @@ public abstract class LockedCache<K, V> extends AbstractCache<K, V> {
/**
* 获得值或清除过期值
* @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<K, V> 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);