多线程访问的简单的基于时间的缓存

问题描述 投票:0回答:1

我有想要缓存一段时间的数据,该数据由多个线程访问。

我为它创建了简单的缓存机制:

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”变量,因为我正在进行复合执行,并且不会在每次访问时锁定它们。

有没有场景

  1. 当第一次调用

    hasExpired
    结果为 false 时,但几毫秒后,另一个线程调用
    getData
    方法
    hasExpired
    结果为 true,进入同步块并更改
    data
    变量引用,因此第一个线程结果陷入未定义的行为?

  2. 锁定是否会阻止指令重组?或者我可以在一个线程中为

    data
    lastAccess
    设置新值,因为
    hasExpired
    为 true,但右侧调用的第二个线程会看到
    lastAccess
    的新值,但数据的旧值?

如果某些点是正确的,正确的修复方法是什么?我需要制作lastAccess 或数据AtomicReferences 吗?每个可能导致不需要的行为的场景都包括

haxExpired
调用,但是用 lock 锁定它基本上会使此类同步。

最重要的是,考虑到数据计算时间超过

validTime
的情况,在将
data
的引用设置为新的
Future
之前我取消了操作,这是否意味着我的
computable
变量必须监听fo
 interrupted
标志正确退出计算?

感谢您的帮助!

java multithreading
1个回答
0
投票

我建议使用用于线程安全单例的完善模式:

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;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.