com.neko233.toolchain.common.cache.objects.impl.TimestampLruCache Maven / Gradle / Ivy
package com.neko233.toolchain.common.cache.objects.impl;
import com.neko233.toolchain.common.cache.objects.AbstractCache;
import com.neko233.toolchain.common.cache.objects.Cache;
import com.neko233.toolchain.common.cache.objects.FlushListener;
import com.neko233.toolchain.common.cache.objects.metrics.CacheMetrics;
import com.neko233.toolchain.common.cache.objects.ref.TimestampSoftRef;
import com.neko233.toolchain.common.annotation.ThreadSafe;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* [Timestamp LRU Cache]
* spend 1 thread to async Scheduler
*
* @author SolarisNeko on 2022-12-12
**/
@ThreadSafe
public class TimestampLruCache extends AbstractCache {
// data
private final ConcurrentMap> cacheMap = new ConcurrentHashMap<>();
// listener
private final List> flushListenerList = new CopyOnWriteArrayList<>();
/**
* scheduler for cache eviction
*/
private final ScheduledExecutorService evictionScheduler = Executors.newScheduledThreadPool(1, r -> {
Thread thread = new Thread(r);
thread.setName(TimestampLruCache.class.getSimpleName() + "-" + System.currentTimeMillis());
return thread;
});
// invalidate settings
public volatile long invalidateMs = TimeUnit.HOURS.toMillis(1);
public volatile long invalidateMaxSize = 100_0000;
// state
private volatile boolean isNeedRefresh = true;
private volatile boolean isNeedFlush = false;
/**
* Constructor
*/
public TimestampLruCache() {
evictionScheduler.scheduleAtFixedRate(() -> {
// 刷盘
flush();
// invalidate
long nowMs = System.currentTimeMillis();
Set toInvalidateKeys = cacheMap.entrySet().stream().filter(entry -> {
TimestampSoftRef value = entry.getValue();
if (value == null) {
return true;
}
return (nowMs - value.getVisitMs()) > invalidateMs;
}).map(Map.Entry::getKey).collect(Collectors.toSet());
this.invalidateAll(toInvalidateKeys);
this.cacheMetrics.addEvictionCount(toInvalidateKeys.size());
}, 0, 5, TimeUnit.SECONDS);
}
private V getByMetrics(TimestampSoftRef vTimestampSoftRef) {
if (vTimestampSoftRef == null) {
this.cacheMetrics.addMissCount(1);
return null;
}
this.cacheMetrics.addHitCount(1);
return vTimestampSoftRef.get();
}
// ------------------------ Cache API -----------------------------------
/**
* cache 过期时间限制
*
* @param invalidateTime 失效时长
* @param timeUnit 单位
* @return this
*/
@Override
public Cache invalidateTime(int invalidateTime, TimeUnit timeUnit) {
this.invalidateMs = timeUnit.toMillis(invalidateTime);
return this;
}
@Override
public Cache invalidateMaxSize(int maxSize) {
this.invalidateMaxSize = maxSize;
return this;
}
/**
* flush listener
*
* @param listener L
* @return this
*/
@Override
public Cache addFlushListener(FlushListener listener) {
flushListenerList.add(listener);
return this;
}
@Override
public Cache removeFlushListener(FlushListener listener) {
flushListenerList.remove(listener);
return this;
}
@Override
public boolean isNeedRefresh() {
return isNeedRefresh;
}
@Override
public Cache isNeedRefresh(boolean value) {
this.isNeedRefresh = value;
return this;
}
@Override
public void refresh() {
super.refresh();
}
@Override
public Cache put(K key, V value) {
cacheMap.put(key, new TimestampSoftRef<>(value));
return this;
}
@Override
public Cache putAll(Map extends K, ? extends V> map) {
return super.putAll(map);
}
@Override
public Cache isNeedFlush(boolean isNeedFlush) {
this.isNeedFlush = isNeedFlush;
return this;
}
@Override
public Cache invalidate(K key) {
cacheMap.remove(key);
return this;
}
@Override
public Cache invalidateAll(Iterable keys) {
return super.invalidateAll(keys);
}
@Override
public Cache invalidateAll() {
cacheMap.clear();
return this;
}
@Override
public V get(K key) {
TimestampSoftRef vTimestampSoftRef = cacheMap.get(key);
return getByMetrics(vTimestampSoftRef);
}
@Override
public ConcurrentMap getAll() {
ConcurrentHashMap map = new ConcurrentHashMap<>();
cacheMap.forEach((k, vRef) -> {
V value = vRef.get();
if (value == null) {
return;
}
map.put(k, value);
});
return map;
}
@Override
public void cleanUp() {
invalidateAll();
}
@Override
public boolean isNeedFlush() {
return this.isNeedFlush;
}
@Override
public void flushCallable(ConcurrentMap snapshot) {
for (FlushListener flushListener : flushListenerList) {
flushListener.handle(snapshot);
}
}
@Override
public V getOrCreate(K key, Supplier supplier) {
return super.getOrCreate(key, supplier);
}
@Override
public V getOrCreate(K key, V newValue) {
TimestampSoftRef valueRef = cacheMap.get(key);
if (valueRef == null) {
this.put(key, newValue);
return newValue;
}
return valueRef.get();
}
@Override
public CacheMetrics snapshotMetrics() {
return this.cacheMetrics.snapshotMetrics();
}
}