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