Merge pull request #1385 from graydovee/v5-dev

缓存降低锁的粒度,提高并发能力
This commit is contained in:
Golden Looly 2021-01-24 21:17:46 +08:00 committed by GitHub
commit 02bd117fd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 40 additions and 6 deletions

View File

@ -7,7 +7,10 @@ import cn.hutool.core.lang.func.Func0;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.StampedLock;
/**
@ -29,6 +32,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
private final StampedLock lock = new StampedLock();
/**
* 写的时候每个key一把锁降低锁的粒度
*/
protected final Map<K, Lock> keyLockMap = new ConcurrentHashMap<>();
/**
* 返回缓存容量{@code 0}表示无大小限制
*/
@ -135,7 +143,9 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
public V get(K key, boolean isUpdateLastAccess, Func0<V> supplier) {
V v = get(key, isUpdateLastAccess);
if (null == v && null != supplier) {
final long stamp = lock.writeLock();
//每个key单独获取一把锁降低锁的粒度提高并发能力
Lock keyLock = keyLockMap.computeIfAbsent(key, k -> new ReentrantLock());
keyLock.lock();
try {
// 双重检查锁
final CacheObj<K, V> co = cacheMap.get(key);
@ -145,12 +155,13 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
} catch (Exception e) {
throw new RuntimeException(e);
}
putWithoutLock(key, v, this.timeout);
put(key, v, this.timeout);
} else {
v = co.get(true);
}
} finally {
lock.unlockWrite(stamp);
keyLock.unlock();
keyLockMap.remove(key);
}
}
return v;

View File

@ -3,14 +3,19 @@ package cn.hutool.cache.test;
import cn.hutool.cache.Cache;
import cn.hutool.cache.impl.FIFOCache;
import cn.hutool.cache.impl.LRUCache;
import cn.hutool.cache.impl.WeakCache;
import cn.hutool.core.lang.Console;
import cn.hutool.core.thread.ConcurrencyTester;
import cn.hutool.core.thread.ThreadUtil;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 缓存单元测试
*
*
* @author looly
*
*/
@ -46,7 +51,7 @@ public class CacheConcurrentTest {
System.out.println("==============================");
ThreadUtil.sleep(10000);
}
@Test
@Ignore
public void lruCacheTest() {
@ -72,7 +77,7 @@ public class CacheConcurrentTest {
}
});
}
ThreadUtil.sleep(5000);
}
@ -82,4 +87,22 @@ public class CacheConcurrentTest {
Console.log(tt);
}
}
@Test
public void effectiveTest() {
// 模拟耗时操作消耗时间
int delay = 2000;
AtomicInteger ai = new AtomicInteger(0);
WeakCache<Integer, Integer> weakCache = new WeakCache<>(60 * 1000);
ConcurrencyTester concurrencyTester = ThreadUtil.concurrencyTest(32, () -> {
int i = ai.incrementAndGet() % 4;
weakCache.get(i, () -> {
ThreadUtil.sleep(delay);
return i;
});
});
long interval = concurrencyTester.getInterval();
// 总耗时应与单次操作耗时在同一个数量级
Assert.assertTrue(interval < delay * 2);
}
}