cache是个好东西,guava的lodingcache是好东西。
问题1:如何自定义过期逻辑
但是lodingcache只能基于访问时间的过期,假如有其他过期逻辑的时候,还是要自己写。
这就开始了我的坑爹之旅。
我有个LocalLock类,是对java的ReentrantLock的封装,ReentrantLock在使用的时候自然是不能过期回收的。
有对应的api:reentrantLock.isLocked()
public boolean canUse() {
long noUseTime = System.currentTimeMillis() - lastUseTime;
if (noUseTime > canUseTime && !reentrantLock.isLocked()) {
return false;
}
return true;
}
然后我就很高兴的写个定时任务来清除过期的了。
this.schedule.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
checkNoUse();
}
}, 10, 10, TimeUnit.SECONDS);
private void checkNoUse() {
Iterator> iterator = cache.entrySet().iterator();
while (iterator.hasNext()) {
LocalLock lock = iterator.next().getValue();
if (!lock.canUse()) {
iterator.remove();
}
}
}
问题2:这个是有并发问题的
- 时序1 定时任务线程可以回收的
- 时序2 业务线程加锁变成不可回收了
- 时序3 定时任务线程remove删除
这样就把不该回收的对象删除了。
解决办法
加synchronized
public synchronized boolean canUse() {
这个synchronized对应两个场景,一个是定时任务,一个就是从缓存中拿对象
private LocalLock getLock(String key) {
LocalLock lock = cache.get(key);
if (lock != null && lock.canUse()) {
return lock;
}
问题3:发现还是有并发问题
从缓存中拿到对象还是可用的,正要开始使用,定时任务判断过期了,回收掉。。
解决办法:
缓存中拿对象的同时,修改最后使用时间,保证一段时间内不会被定时任务回收。
愉快的修改代码为:
public synchronized boolean canUseAndRefresh() {
long noUseTime = System.currentTimeMillis() - lastUseTime;
if (noUseTime > canUseTime && !reentrantLock.isLocked()) {
return false;
}
lastUseTime = System.currentTimeMillis();
return true;
}
问题4:发现定时任务永远回收不了
因为定时任务每次都修改了最后访问时间,相当于定时任务在续命了,永远不会过期。
解决办法:
canUseAndRefresh加个参数区分两个逻辑,定时任务就不修改最后访问时间了。
public synchronized boolean canUseAndRefresh(boolean refresh) {
long noUseTime = System.currentTimeMillis() - lastUseTime;
if (noUseTime > canUseTime && !reentrantLock.isLocked()) {
return false;
}
if (refresh) {
lastUseTime = System.currentTimeMillis();
}
return true;
}