feat[LazyCache]: lazy detect the expiration time

This commit is contained in:
godotg committed 2024-03-25 19:07:22 +08:00
1 parent ac0bb9214f
commit 66d5735bff
2 files changed
+189

No files matched your search

@@ -0,0 +1,122 @@
package com.zfoo.orm.util;
import com.zfoo.scheduler.util.TimeUtils;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
/**
* lazy detect the expiration time
*
* @author godotg
*/
public class LazyCache<K, V> {
private static class ValueCache<V> {
public volatile V value;
public volatile long expireTime;
}
private AtomicLong expireCheckTimeAtomic;
private volatile long minExpireTime;
private long expireAfterAccessMillis;
private long expireCheckInterval;
private int maximumSize;
private ConcurrentMap<K, ValueCache<V>> cacheMap;
private BiConsumer<K, V> removeCallback = (k, v) -> {
};
public LazyCache(int maximumSize, long expireAfterAccessMillis, long expireCheckIntervalMillis, BiConsumer<K, V> removeCallback) {
this.maximumSize = maximumSize;
this.expireAfterAccessMillis = expireAfterAccessMillis;
this.expireCheckInterval = expireCheckIntervalMillis;
this.expireCheckTimeAtomic = new AtomicLong(TimeUtils.now() + expireCheckInterval);
this.cacheMap = new ConcurrentHashMap<>(Math.max(maximumSize / 16, 512));
if (removeCallback != null) {
this.removeCallback = removeCallback;
}
}
/**
* If the cache previously contained a value associated with the key, the old value is replaced by the new value.
*/
public void put(K key, V value) {
var valueCache = new ValueCache<V>();
valueCache.value = value;
valueCache.expireTime = TimeUtils.now();
cacheMap.put(key, valueCache);
checkMaximumSize();
checkExpire();
}
public V get(K key) {
checkExpire();
var valueCache = cacheMap.get(key);
if (valueCache == null) {
return null;
}
if (valueCache.expireTime < TimeUtils.now()) {
remove(key);
return null;
}
valueCache.expireTime = TimeUtils.now() + expireAfterAccessMillis;
return valueCache.value;
}
public void remove(K key) {
if (key == null) {
return;
}
var valueCache = cacheMap.remove(key);
if (valueCache != null) {
removeCallback.accept(key, valueCache.value);
}
}
// -----------------------------------------------------------------------------------------------------------------
private void checkMaximumSize() {
if (cacheMap.size() <= maximumSize) {
return;
}
K minKey = null;
var minTimestamp = Long.MAX_VALUE;
for (var entry : cacheMap.entrySet()) {
if (entry.getValue().expireTime < minTimestamp) {
minKey = entry.getKey();
minTimestamp = entry.getValue().expireTime;
}
}
this.minExpireTime = minTimestamp;
remove(minKey);
checkMaximumSize();
}
private void checkExpire() {
var now = TimeUtils.now();
var expireCheckTime = expireCheckTimeAtomic.get();
if (now > expireCheckTime) {
if (expireCheckTimeAtomic.compareAndSet(expireCheckTime, now + expireCheckInterval)) {
if (now > this.minExpireTime) {
var minTimestamp = Long.MAX_VALUE;
for (var entry : cacheMap.entrySet()) {
var expireTime = entry.getValue().expireTime;
if (expireTime < now) {
remove(entry.getKey());
}
if (expireTime < minTimestamp) {
minTimestamp = expireTime;
}
}
this.minExpireTime = minTimestamp;
}
}
}
}
}
+67
View File
@@ -0,0 +1,67 @@
package com.zfoo.orm.cache;
import com.zfoo.orm.util.LazyCache;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.protocol.util.ThreadUtils;
import com.zfoo.scheduler.util.TimeUtils;
import org.junit.Ignore;
import org.junit.Test;
import java.util.function.BiConsumer;
/**
* @author godotg
*/
@Ignore
public class LazyCacheTest {
private static final BiConsumer<Integer, String> myRemoveCallback = new BiConsumer<Integer, String>() {
@Override
public void accept(Integer key, String value) {
System.out.println(StringUtils.format("remove key:[{}] value:[{}]", key, value));
}
};
@Test
public void putTest() {
var lazyCache = new LazyCache<Integer, String>(3, 10 * TimeUtils.MILLIS_PER_SECOND, 5 * TimeUtils.MILLIS_PER_SECOND, myRemoveCallback);
lazyCache.put(1, "a");
lazyCache.put(2, "b");
ThreadUtils.sleep(1000);
lazyCache.put(3, "c");
ThreadUtils.sleep(1000);
lazyCache.put(4, "d");
ThreadUtils.sleep(1000);
lazyCache.put(5, "e");
}
@Test
public void expireTest() {
var lazyCache = new LazyCache<Integer, String>(10, 10 * TimeUtils.MILLIS_PER_SECOND, 5 * TimeUtils.MILLIS_PER_SECOND, myRemoveCallback);
lazyCache.put(1, "a");
lazyCache.put(2, "b");
lazyCache.put(3, "c");
lazyCache.put(4, "d");
lazyCache.put(5, "e");
ThreadUtils.sleep(11 * TimeUtils.MILLIS_PER_SECOND);
System.out.println(lazyCache.get(1));
System.out.println(lazyCache.get(2));
System.out.println(lazyCache.get(3));
System.out.println(lazyCache.get(4));
System.out.println(lazyCache.get(5));
}
@Test
public void batchTest() {
var lazyCache = new LazyCache<Integer, String>(1_0000, 10 * TimeUtils.MILLIS_PER_SECOND, 5 * TimeUtils.MILLIS_PER_SECOND, myRemoveCallback);
for (int i = 0; i < 1000_0000; i++) {
lazyCache.put(i, String.valueOf(i));
if (i % 1_0000 == 0) {
ThreadUtils.sleep(5 * TimeUtils.MILLIS_PER_SECOND);
}
}
}
}