All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 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();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy