3
package com.common.helper;


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.lang.Nullable;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * 本地缓存管理器
 * <pre>
 *     v1.1
 *       - getByNameSpace
 *       - isExpired(..) 判断是否已经死亡, 包含物理死亡和逻辑死亡.
 *
 *     v1.0
 *       - 增加构造, 可指定线程数
 *
 *     v0.0.1
 *       - 测试期
 * </pre>
 *
 * @author Nisus Liu
 * @version 1.1
 * @email liuhejun108@163.com
 * @date 2018/11/7 17:55
 */
@Slf4j
public abstract class LocalCacheManager<T> {
    public static final String NAMESPACE_KEY_JOIN = "::";
    /**
     * 缓存池子
     */
    protected Map<String, T> caches = new ConcurrentHashMap<String, T>();
    /**
     * 清除缓存的定时延迟任务(刽子手)
     */
    protected Map<String, Headsman> headsmans = new ConcurrentHashMap<String, Headsman>();
    protected ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(5);

    public LocalCacheManager() {
    }

    /**
     * @param poolSize 指定用于清除定时任务的线程数
     */
    public LocalCacheManager(int poolSize) {
        scheduler.setCorePoolSize(poolSize);
    }


    /**
     * 获取指定key的缓存值
     *
     * @param key
     * @return
     */
    public T get(@Nullable String nameSpace, String key) {
        if (StringUtil.isEmpty(key)) {
            return null;
        }
        if (!StringUtil.isEmpty(nameSpace)) {
            //key实际上是原生key加上命名空间前缀
            key = joinNameSpaceAndKey(nameSpace, key);
        }
        if (isExpired(key)) return null;

        return caches.get(key);
    }

    /**
     * 判断是否死亡(实际死亡/逻辑死亡)
     * <p>逻辑死亡: 时间上看已经过期, 但是由于秦楚线程阻塞, 还没有来得及清除</p>
     *
     * @param nskey joinNameSpaceAndKey
     * @return
     */
    private boolean isExpired(String nskey) {
        //防止延迟清除任务阻塞, 导致生存时间误差, 这里实现逻辑清除
        Headsman headsman = headsmans.get(nskey);
        if (headsman != null) {
            //死亡时间 <= 当前时间, 应该设置为逻辑死亡, 返回null
            if (headsman.triggerTime <= System.nanoTime()) {
                // ?已经逻辑死亡, 但实际没有死亡的值是否要在这里清除呢?
                return true;
            } else {
                return false;
            }
        } // else: 物理死亡
        return !caches.containsKey(nskey);
    }

    private String joinNameSpaceAndKey(String nameSpace, String key) {
        if (StringUtils.isBlank(key)) {
            log.debug("`key` is NULL, return");
            return null;
        }
        if (!StringUtil.isEmpty(nameSpace)) {
            return nameSpace + NAMESPACE_KEY_JOIN + key;
        }
        return key;
    }

    /**
     * 增加OR更新缓存
     *
     * @param key
     * @param value
     */
    public boolean put(@Nullable String nameSpace, @NotNull String key, T value) {
        key = joinNameSpaceAndKey(nameSpace, key);
        if (key == null) {
            return false;
        }
        caches.put(key, value);
        return true;
    }

    /**
     * 更加OR更新缓存(可设置生存时间TTL,Time To Live)
     * <pre>
     *     Note: 注意避免并发对同一个 key 设置TTL, 结果比较随机, 谁最后执行, 就以谁的TTL设置为准.
     * </pre>
     *
     * @param key
     * @param value
     * @param ttl
     * @param unit
     */
    @Deprecated
    public void put(@Nullable String nameSpace, String key, T value, long ttl, TimeUnit unit) {
        String nsKey = joinNameSpaceAndKey(nameSpace, key);
        put(null, nsKey, value);
        long triggerTime = System.nanoTime() + unit.toNanos(ttl);

        //若此 key 已经有对应的杀死任务, 需要替换掉, 更新生存时间, 以最新的为准
        Headsman headsman = headsmans.get(nsKey);
        if (headsman == null) {         //增加缓存
            headsman = new Headsman();
            headsmans.put(nsKey, headsman);
            headsman.task = new Runnable() {
                @Override
                public void run() {
                    //指定时间后清除此 nsKey
                    T rm = caches.remove(nsKey);
                    log.trace("cache expired, key: {}, value: {}", nsKey, rm);
                }
            };

        } else {                    //更新缓存
            //对于已经有设置过缓存的 nsKey, 任务用已有的, scheduledFuture 先取消旧的, 在new新的
            /*如果任务运行之前调用了该方法,那么任务就不会被运行;
            如果任务已经完成或者已经被取消,那么该方法方法不起作用;
            如果任务正在运行,并且 cancel 传入参数为 true,那么便会去终止与 Future 关联的任务。*/
            headsman.scheduledFuture.cancel(true);
        }
        headsman.scheduledFuture = new FutureTask(headsman.task, null);
        //时间转换成毫秒
        headsman.triggerTime = triggerTime;

        scheduler.schedule(headsman.scheduledFuture, ttl, unit);
    }

    public void put(@Nullable String nameSpace, String key, T value, Duration ttl) {
        String nsKey = joinNameSpaceAndKey(nameSpace, key);
        put(null, nsKey, value);

        long triggerTime = System.nanoTime() + ttl.toNanos();

        //若此 key 已经有对应的杀死任务, 需要替换掉, 更新生存时间, 以最新的为准
        Headsman headsman = headsmans.get(nsKey);
        if (headsman == null) {         //增加缓存
            headsman = new Headsman();
            headsmans.put(nsKey, headsman);
            headsman.task = new Runnable() {
                @Override
                public void run() {
                    //指定时间后清除此 nsKey
                    T rm = caches.remove(nsKey);
                    log.trace("cache expired, key: {}, value: {}", nsKey, rm);
                }
            };

        } else {                    //更新缓存
            //对于已经有设置过缓存的 nsKey, 任务用已有的, scheduledFuture 先取消旧的, 在new新的
            /*如果任务运行之前调用了该方法,那么任务就不会被运行;
            如果任务已经完成或者已经被取消,那么该方法方法不起作用;
            如果任务正在运行,并且 cancel 传入参数为 true,那么便会去终止与 Future 关联的任务。*/
            headsman.scheduledFuture.cancel(true);
        }
        headsman.scheduledFuture = new FutureTask(headsman.task, null);
        //时间转换成毫秒
        headsman.triggerTime = triggerTime;

        scheduler.schedule(headsman.scheduledFuture, ttl.toMillis(), TimeUnit.MILLISECONDS);
    }


    public T evictCache(String nameSpace, String key) {
        if (key == null) {
            return null;
        }
        String nsKey = joinNameSpaceAndKey(nameSpace, key);
        Headsman hsm = headsmans.remove(nsKey);
        if (hsm != null) hsm.scheduledFuture.cancel(true);
        return caches.remove(nsKey);
    }


    /**
     * 清空当前命名空间下的所有value
     */
    public void evictCache(String nameSpace) {
        String prefix = nameSpace + NAMESPACE_KEY_JOIN;
        caches.forEach((k, v) -> {
            if (k.startsWith(prefix)) {
                evictCache(null, k);
            }
        });
    }

    public void evictAllCache() {
        caches.clear();
    }

    /**
     * 暴露指定命名空间下所有的缓存
     *
     * @param nameSpace
     * @return
     */
    public List<T> getByNameSpace(String nameSpace) {
        String prefix = nameSpace + NAMESPACE_KEY_JOIN;
        List<T> nsVals = new ArrayList<>();
        caches.forEach((k, v) -> {
            if (k.startsWith(prefix)) {
                if (!isExpired(k)) {
                    nsVals.add(v);
                }
            }
        });

        return nsVals;
    }


    class Headsman {
        /**
         * 死亡时间, 纳秒值
         */
        public long triggerTime;
        /**
         * 对key更新缓存时, 旧的已有的会取消, 重新设置新的.
         */
        public FutureTask scheduledFuture;
        /**
         * 对于每个 key, task是单例的
         */
        public Runnable task;
    }


}


Nisus
200 声望6 粉丝

如果生命只有一次, 我愿意尝试各种可能!