mirror of
https://github.com/tiennm99/zfoo.git
synced 2026-10-13 04:15:22 +00:00
feat[LazyCache]: lazy detect the expiration time
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user