MelonBlog

java内存缓存工具类(小项目平替redis)

小项目里想用到缓存和加锁功能,但是又不想额外装一个redis中间件,毕竟小项目都是单体应用,对性能和持久化需求不是非常高,而且都是单点部署,所以想写一个工具类来解决这个问题。

实现

第一个需要有一个对象来存放缓存的内容,并且存储key的过期时间

@Data
public class CacheItem {
    private Object value;
    private Long expireAt;
    public boolean isExpired() {
        if (expireAt == null) {
            return false;
        }
        return System.currentTimeMillis() > expireAt;
    }
    public CacheItem(Object value, Long expireAt) {
        this.value = value;
        this.expireAt = expireAt;
    }
}

读、写和删除 最好是原子的操作,所以要对同一key的读写操作加锁

private enum Operation {
    SET, GET, REMOVE
}
private Object operate(Operation operation, String key, Object value, Long duration, TimeUnit timeUnit, boolean nx) {
    synchronized (key.intern()) {
        if (operation == Operation.SET) {
            if (nx) {
                if (cache.containsKey(key)) {
                    return null;
                }
            }
            if (duration == null) {
                cache.put(key, new CacheItem(value, null));
            } else if (timeUnit == null) {
                cache.put(key, new CacheItem(value, System.currentTimeMillis() + duration));
            } else {
                cache.put(key, new CacheItem(value, System.currentTimeMillis() + timeUnit.toMillis(duration)));
            }
            return value;
        } else if (operation == Operation.GET) {
            CacheItem cacheItem = cache.get(key);
            if (cacheItem == null) {
                return null;
            }
            if (cacheItem.isExpired()) {
                System.out.println("get - remove key: " + key);
                cache.remove(key);
                return null;
            }
            return cacheItem.getValue();
        } else if (operation == Operation.REMOVE) {
            cache.remove(key);
            return null;
        } else {
            throw new RuntimeException("不支持的操作");
        }
    }
}

得有一个时钟功能来清理过期的key

private void tick() {
    new Thread(() -> {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            cache.forEach((key, value) -> {
                if (value.isExpired()) {
                    cache.remove(key);
                    System.out.println("tick - remove key: " + key);
                }
            });
        }
    }).start();
}

测试

public static void main(String[] args) {
    MemoryCacheUtils cacheUtils = MemoryCacheUtils.INSTANCE;
    System.out.printf("设置缓存: a -> %s%n", cacheUtils.set("a", "b"));
    System.out.printf("读取缓存a: %s%n", cacheUtils.getString("a"));
    System.out.printf("设置缓存: a -> %s%n", cacheUtils.set("a", "c"));
    System.out.printf("读取缓存a: %s%n", cacheUtils.getString("a"));
    System.out.printf("设置缓存: a -> %s  有效期1秒%n", cacheUtils.set("a", "c", 1000L));
    System.out.printf("读取缓存a: %s%n", cacheUtils.getString("a"));
    try {
        System.out.println("等待1秒");
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.printf("读取缓存a: %s%n", cacheUtils.getString("a"));
    System.out.printf("lock-a加锁: %s%n", cacheUtils.setNx("lock-a", "", 1000L));
    System.out.printf("lock-a加锁: %s%n", cacheUtils.setNx("lock-a", "", 1000L));
    System.out.printf("lock-b加锁: %s%n", cacheUtils.setNx("lock-b", "", 1000L));
    System.out.println("移除lock-b");
    cacheUtils.remove("lock-b");
    System.out.printf("lock-b加锁: %s%n", cacheUtils.setNx("lock-b", "", 1000L));
}

输出

设置缓存: a -> b
读取缓存a: b
设置缓存: a -> c
读取缓存a: c
设置缓存: a -> c 有效期1
读取缓存a: c
等待1
get - remove key: a
读取缓存a: null
lock-a加锁: true
lock-a加锁: false
lock-b加锁: true
lock-b加锁: false
tick - remove key: lock-a
tick - remove key: lock-b

完整的类

@Slf4j
@Data
public class MemoryCacheUtils {
    public static final MemoryCacheUtils INSTANCE = new MemoryCacheUtils();
    private MemoryCacheUtils() {
        this.tick();
    }
    @Data
    public static class CacheItem {
        private Object value;
        private Long expireAt;
        public boolean isExpired() {
            if (expireAt == null) {
                return false;
            }
            return System.currentTimeMillis() > expireAt;
        }
        public CacheItem(Object value, Long expireAt) {
            this.value = value;
            this.expireAt = expireAt;
        }
    }
    private enum Operation {
        SET, GET, REMOVE
    }
    private final Map<String, CacheItem> cache = new ConcurrentHashMap<>();
    private Object operate(Operation operation, String key, Object value, Long duration, TimeUnit timeUnit, boolean nx) {
        synchronized (key.intern()) {
            if (operation == Operation.SET) {
                if (nx) {
                    if (cache.containsKey(key)) {
                        return null;
                    }
                }
                if (duration == null) {
                    cache.put(key, new CacheItem(value, null));
                } else if (timeUnit == null) {
                    cache.put(key, new CacheItem(value, System.currentTimeMillis() + duration));
                } else {
                    cache.put(key, new CacheItem(value, System.currentTimeMillis() + timeUnit.toMillis(duration)));
                }
                return value;
            } else if (operation == Operation.GET) {
                CacheItem cacheItem = cache.get(key);
                if (cacheItem == null) {
                    return null;
                }
                if (cacheItem.isExpired()) {
                    System.out.println("get - remove key: " + key);
                    cache.remove(key);
                    return null;
                }
                return cacheItem.getValue();
            } else if (operation == Operation.REMOVE) {
                cache.remove(key);
                return null;
            } else {
                throw new RuntimeException("不支持的操作");
            }
        }
    }
    private void tick() {
        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                cache.forEach((key, value) -> {
                    if (value.isExpired()) {
                        cache.remove(key);
                        System.out.println("tick - remove key: " + key);
                    }
                });
            }
        }).start();
    }
    public String getString(String key) {
        Object value = operate(Operation.GET, key, null, null, null, false);
        if (value == null) {
            return null;
        }
        return (String) value;
    }
    public Integer getInteger(String key) {
        Object value = operate(Operation.GET, key, null, null, null, false);
        if (value == null) {
            return null;
        }
        return (Integer) value;
    }
    public boolean getLong(String key) {
        Object value = operate(Operation.GET, key, null, null, null, false);
        if (value == null) {
            return false;
        }
        return (Boolean) value;
    }
    public boolean getBoolean(String key) {
        Object value = operate(Operation.GET, key, null, null, null, false);
        if (value == null) {
            return false;
        }
        return (Boolean) value;
    }
    public boolean set(String key, Object value) {
        return value.equals(operate(Operation.SET, key, value, null, null, false));
    }
    public boolean set(String key, Object value, Long duration) {
        return value.equals(operate(Operation.SET, key, value, duration, TimeUnit.MILLISECONDS, false));
    }
    public boolean set(String key, Object value, Long duration, TimeUnit timeUnit) {
        return value.equals(operate(Operation.SET, key, value, duration, timeUnit, false));
    }
    public Object getObject(String key) {
        return operate(Operation.GET, key, null, null, null, false);
    }
    public boolean setNx(String key, Object value, Long duration) {
        return value.equals(operate(Operation.SET, key, value, duration, null, true));
    }
    public boolean setNx(String key, Object value, Long duration, TimeUnit timeUnit) {
        return value.equals(operate(Operation.SET, key, value, duration, timeUnit, true));
    }
    public void remove(String key) {
        operate(Operation.REMOVE, key, null, null, null, false);
    }
    public static void main(String[] args) {
        MemoryCacheUtils cacheUtils = MemoryCacheUtils.INSTANCE;
        System.out.printf("设置缓存: a -> %s%n", cacheUtils.set("a", "b"));
        System.out.printf("读取缓存a: %s%n", cacheUtils.getString("a"));
        System.out.printf("设置缓存: a -> %s%n", cacheUtils.set("a", "c"));
        System.out.printf("读取缓存a: %s%n", cacheUtils.getString("a"));
        System.out.printf("设置缓存: a -> %s  有效期1秒%n", cacheUtils.set("a", "c", 1000L));
        System.out.printf("读取缓存a: %s%n", cacheUtils.getString("a"));
        try {
            System.out.println("等待1秒");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("读取缓存a: %s%n", cacheUtils.getString("a"));
        System.out.printf("lock-a加锁: %s%n", cacheUtils.setNx("lock-a", "", 1000L));
        System.out.printf("lock-a加锁: %s%n", cacheUtils.setNx("lock-a", "", 1000L));
        System.out.printf("lock-b加锁: %s%n", cacheUtils.setNx("lock-b", "", 1000L));
        System.out.println("移除lock-b");
        cacheUtils.remove("lock-b");
        System.out.printf("lock-b加锁: %s%n", cacheUtils.setNx("lock-b", "", 1000L));
    }
}