我有想要缓存一段时间的数据,该数据由多个线程访问。
我为它创建了简单的缓存机制:
public class ExpirationCache<T> {
private final Supplier<T> computable;
private final long validTime;
private final Lock lock = new ReentrantLock();
private LocalDate lastAccess;
private Future<T> data;
public ExpirationCache(Supplier<T> computable, long validTime) {
this.computable = computable;
this.validTime = validTime;
}
public boolean hasExpired() {
return LocalDate.now().isAfter(lastAccess.plus(validTime, ChronoUnit.MILLIS));
}
public T getData() throws InterruptedException {
while (true) {
if (hasExpired()) {
FutureTask<T> ft = new FutureTask<T>(computable::get);
try {
lock.lock();
if (hasExpired()) {
data.cancel(true);
data = ft;
}
} finally {
lastAccess = LocalDate.now();
lock.unlock();
}
}
try {
return data.get();
} catch (CancellationException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}
}
我的问题是关于“data”和“lastAccess”变量,因为我正在进行复合执行,并且不会在每次访问时锁定它们。
有没有场景
当第一次调用
hasExpired
结果为 false 时,但几毫秒后,另一个线程调用 getData
方法 hasExpired
结果为 true,进入同步块并更改 data
变量引用,因此第一个线程结果陷入未定义的行为?
锁定是否会阻止指令重组?或者我可以在一个线程中为
data
和 lastAccess
设置新值,因为 hasExpired
为 true,但右侧调用的第二个线程会看到 lastAccess
的新值,但数据的旧值?
如果某些点是正确的,正确的修复方法是什么?我需要制作lastAccess 或数据AtomicReferences 吗?每个可能导致不需要的行为的场景都包括
haxExpired
调用,但是用 lock 锁定它基本上会使此类同步。
最重要的是,考虑到数据计算时间超过
validTime
的情况,在将data
的引用设置为新的Future
之前我取消了操作,这是否意味着我的computable
变量必须监听fo interrupted
标志正确退出计算?
感谢您的帮助!
我建议使用用于线程安全单例的完善模式:
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
所以在你的情况下是:
public class ExpirationCache<T> {
...
public T getData() throws InterruptedException {
if (data == null || isExpired()) {
synchronized(lock) {
if (data == null || isExpired()) {
data = computable.get();
lastAccess = Instant.now();
}
}
}
return data;
}
}