java内存缓存工具类(小项目平替redis)
小项目里想用到缓存和加锁功能,但是又不想额外装一个redis中间件,毕竟小项目都是单体应用,对性能和持久化需求不是非常高,而且都是单点部署,所以想写一个工具类来解决这个问题。
实现
第一个需要有一个对象来存放缓存的内容,并且存储key的过期时间
@Datapublic 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: nulllock-a加锁: truelock-a加锁: falselock-b加锁: truelock-b加锁: falsetick - remove key: lock-atick - remove key: lock-b完整的类
@Slf4j@Datapublic 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)); }}