Java本地缓存

概述

编写一个Java本地缓存类,以及核心方法的单元测试。

Must

实现基本缓存操作 get, set, remove。

读写操作保证线程安全。

支持设置缓存过期时间, TTL或者TTI。

maven 打包编译 (https://maven.apache.org/)。

junit单元测试 (https://junit.org/)。

核心方法变量添加 javadoc。

Better

读写拷贝

持久化

内存回收策略(LRU, FIFO)

Result

实现基本缓存操作 get, set, remove。

读写操作保证线程安全。

支持设置缓存过期时间, TTL或者TTI。

maven 打包编译 (https://maven.apache.org/)。

junit单元测试 (https://junit.org/)。

核心方法变量添加 javadoc。

内存回收策略 FIFO

实现

大家好像都用 ConcurrentHashMap 来当作本地缓存的 store

可以查看jdk 1.8 相关文档

ConcurrentHashMap支持一组顺序和并行批量操作,与大多数Stream方法不同,它们被设计为安全并且经常明智地应用,即使是由其他线程同时更新的映射;

例如,当计算共享注册表中的值的快照摘要时。 有三种操作,每种具有四种形式,接受键,值,条目和(键,值)参数和/或返回值的函数。 由于ConcurrentHashMap的元素不以任何特定的方式排序,并且可能会在不同的并行执行中以不同的顺序进行处理,因此提供的函数的正确性不应取决于任何排序,也不应依赖于可能瞬时变化的任何其他对象或值计算进行中; 除了每一个行动,理想情况下都是无副作用的。

ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 ReentrantLock。不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment

基本要求

既然是本地缓存

  1. 一个用于保存缓存的空间:ConcurrentHashMap。
  2. 一个用于监控缓存的线程:达到移除失效缓存的目的。
  3. 一个用于保存缓存内容和缓存相关特定属性的类:比如缓存的键值、时间戳、失效时间、命中次数等。
  4. 内存回收策略:先进先出(FIFO),最近最久未使用(LRU )等。
  5. 缓存失效时间设置:TTL,TTI等。
  6. 单元测试
  7. 读写克隆?
  8. 序列化保存做持久化?
  9. 等等。。

缓存移除策略

即如果缓存满了,从缓存中移除数据的策略;常见的有LFU、LRU、FIFO:

FIFO(First In First Out):先进先出算法,即先放入缓存的先被移除;

LRU(Least Recently Used):最久未使用算法,使用时间距离现在最久的那个被移除;

LFU(Least Frequently Used):最近最少使用算法,一定时间段内使用次数(频率)最少的那个被移除;

过期失效策略

TTL(Time To Live )

存活期,即从缓存中创建时间点开始直到它到期的一个时间段(不管在这个时间段内有没有访问都将过期)

TTI(Time To Idle)

空闲期,即一个数据多久没被访问将从缓存中移除的时间。

https://github.com/XtrueT/LocalCache-java

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;


/**
 * @Author: 霁
 * @Date: 2020/7/7 20:32
 */
public class LocalCache implements Serializable {
    /***
     * 默认缓存过期时间
     */
    private static long DEFAULT_TIMEOUT = 3600 * 1000;
    /***
     * 定时任务执行间隔
     * 单位 秒 不小于 1
     */
    private static final long TASK_TIME = 10;
    /***
     * 缓存空间大小
     */
    private static int DEFAULT_SIZE = 512;
    /***
     * 考虑高并发情况下数据安全使用ConcurrentHashMap
     */
    private static final ConcurrentHashMap<String, CacheEntity> concurrentHashMap = new ConcurrentHashMap<>(DEFAULT_SIZE);

    private static LocalCache localCache = null;

    static ConcurrentHashMap<String, CacheEntity> getConcurrentHashMap() {
        return concurrentHashMap;
    }

    static long getDefaultTimeout() {
        return DEFAULT_TIMEOUT;
    }

    static int getDefaultSize() {
        return DEFAULT_SIZE;
    }

    static void setDefaultTimeout(long defaultTimeout) {
        DEFAULT_TIMEOUT = defaultTimeout;
    }

    static void setDefaultSize(int defaultSize) {
        DEFAULT_SIZE = defaultSize;
    }

    private LocalCache() {
    }

    /***
     * 单例模式
     * @return LocalCache实例
     */
    static LocalCache getInstance() {
        if (localCache == null) {
            localCache = new LocalCache();
            new Thread(new TimeoutTimerThread()).start();
        }
        return localCache;
    }

    /***
     * 设置缓存
     * @param key 键
     * @param value 值
     * @param expire 过期时间
     */
    public synchronized void set(String key, Object value, long expire) throws InterruptedException {
        // 已经存在则更新
        if (concurrentHashMap.containsKey(key)) {
            CacheEntity cacheEntity = concurrentHashMap.get(key);
            cacheEntity.setExpire(expire);
            cacheEntity.setTimeStamp(System.currentTimeMillis());
            cacheEntity.setValue(value);
            return;
        }
        if (localCache.checkIsFull()) {
            concurrentHashMap.remove(fifo());
        }
        TimeUnit.SECONDS.sleep(1);
        concurrentHashMap.put(key, new CacheEntity(key, value, expire));
    }

    /***
     * 设置缓存
     * @param key 键
     * @param value 值
     */
    public synchronized void set(String key, Object value) throws InterruptedException {
        // 已经存在则更新
        if (concurrentHashMap.containsKey(key)) {
            CacheEntity cacheEntity = concurrentHashMap.get(key);
            cacheEntity.setTimeStamp(System.currentTimeMillis());
            cacheEntity.setValue(value);
            System.out.println("更新缓存:" + cacheEntity);
            return;
        }
        if (localCache.checkIsFull()) {
            concurrentHashMap.remove(fifo());
        }
        TimeUnit.SECONDS.sleep(1);
        concurrentHashMap.put(key, new CacheEntity(key, value));
    }

    /***
     * 获取缓存
     * @param key 键
     * @return 返回缓存的值
     */
    public synchronized Object get(String key) {
        CacheEntity cacheEntity = concurrentHashMap.get(key);
        if (cacheEntity == null) {
            return null;
        }
        if (localCache.checkIsExpire(cacheEntity)) {
            concurrentHashMap.remove(key);
            System.out.println("移除失效缓存:" + cacheEntity);
            return null;
        }
        return cacheEntity.getValue();
    }

    /***
     * 移除缓存
     * @param key 键
     */
    public synchronized void remove(String key) {
        concurrentHashMap.remove(key);
    }

    /***
     * 清空缓存
     */
    public void clear() {
        concurrentHashMap.clear();
    }

    /***
     * 检查是否达到最大缓存空间
     * @return true or false
     */
    private boolean checkIsFull() {
        System.out.println("检查是否已达最大缓存:");
        return concurrentHashMap.size() == DEFAULT_SIZE;
    }

    /***
     * 检查是否失效
     * @param cacheEntity 缓存对象
     * @return true or false
     */
    private boolean checkIsExpire(CacheEntity cacheEntity) {
        if (cacheEntity.getExpire() == -1) {
            return false;
        }
        return (System.currentTimeMillis() - cacheEntity.getTimeStamp()) >= cacheEntity.getExpire();
    }

    /***
     * 先进先出
     * @return 应被移除的键
     */
    private String fifo() {
        List<CacheEntity> cacheEntities = new ArrayList<>();
        for (Object k : ((ConcurrentHashMap) LocalCache.concurrentHashMap).keySet()) {
            CacheEntity cacheEntity = (CacheEntity) ((ConcurrentHashMap) LocalCache.concurrentHashMap).get(k);
            cacheEntities.add(cacheEntity);
        }
        cacheEntities.sort((o1, o2) -> Long.compare(o1.getTimeStamp(), o2.getTimeStamp()));
        // System.out.println("排序后缓存列表:"+cacheEntities);
        System.out.println("已达最大缓存,移除最先进入的缓存:" + cacheEntities.get(0).getKey());
        return cacheEntities.get(0).getKey();
    }

    /***
     * 定时器线程类
     * 检查缓存过期
     */
    private static class TimeoutTimerThread implements Runnable {

        @Override
        @SuppressWarnings("InfiniteLoopStatement")
        public void run() {
            while (true) {
                try {
                    if (!concurrentHashMap.isEmpty()) {
                        TimeUnit.SECONDS.sleep(TASK_TIME);
                        System.out.println("检查失效缓存:");
                        System.out.println("当前缓存使用" + concurrentHashMap.size() + "剩余" + (DEFAULT_SIZE - concurrentHashMap.size()));
                        System.out.println("当前缓存空间数据:" + concurrentHashMap);
                        for (String key : concurrentHashMap.keySet()) {
                            CacheEntity cacheEntity = concurrentHashMap.get(key);
                            if (localCache.checkIsExpire(cacheEntity)) {
                                concurrentHashMap.remove(key);
                                System.out.println("移除失效缓存:" + cacheEntity);
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /***
     * 缓存对象类
     */
    private static class CacheEntity implements Serializable {

        /***
         * 对应的键
         */
        private String key;
        /***
         * 缓存的值
         */
        private Object value;
        /***
         * 创建时间戳
         */
        private long timeStamp = System.currentTimeMillis();
        /***
         * 失效时间
         */
        private long expire = DEFAULT_TIMEOUT;

        String getKey() {
            return key;
        }

        Object getValue() {
            return value;
        }

        long getTimeStamp() {
            return timeStamp;
        }

        long getExpire() {
            return expire;
        }

        void setValue(Object value) {
            this.value = value;
        }

        void setTimeStamp(long timeStamp) {
            this.timeStamp = timeStamp;
        }

        void setExpire(long expire) {
            this.expire = expire;
        }

        CacheEntity(String key, Object value, long expire) {
            this.key = key;
            this.value = value;
            this.expire = expire;
        }

        CacheEntity(String key, Object value) {
            this.key = key;
            this.value = value;
        }

        @Override
        public String toString() {
            return "[" + key + "," + value + "," + timeStamp + "," + expire + ']';
        }
    }
}


奖励一下