- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我最近正在寻找一种方法来为常规对象实现双缓冲线程安全缓存。
之所以出现这种需求,是因为我们有一些缓存的数据结构,每个请求都会多次命中这些数据结构,并且需要从非常大的文档(1s+ 解码时间)中的缓存中重新加载,并且我们无法承受让所有请求都被缓存的后果。每分钟都延迟那么久。
由于我找不到一个好的线程安全实现,所以我自己编写了它,现在我想知道它是否正确以及是否可以做得更小......这里是:
package nl.trimpe.michiel
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Abstract class implementing a double buffered cache for a single object.
*
* Implementing classes can load the object to be cached by implementing the
* {@link #retrieve()} method.
*
* @param <T>
* The type of the object to be cached.
*/
public abstract class DoublyBufferedCache<T> {
private static final Log log = LogFactory.getLog(DoublyBufferedCache.class);
private Long timeToLive;
private long lastRetrieval;
private T cachedObject;
private Object lock = new Object();
private volatile Boolean isLoading = false;
public T getCachedObject() {
checkForReload();
return cachedObject;
}
private void checkForReload() {
if (cachedObject == null || isExpired()) {
if (!isReloading()) {
synchronized (lock) {
// Recheck expiration because another thread might have
// refreshed the cache before we were allowed into the
// synchronized block.
if (isExpired()) {
isLoading = true;
try {
cachedObject = retrieve();
lastRetrieval = System.currentTimeMillis();
} catch (Exception e) {
log.error("Exception occurred retrieving cached object", e);
} finally {
isLoading = false;
}
}
}
}
}
}
protected abstract T retrieve() throws Exception;
private boolean isExpired() {
return (timeToLive > 0) ? ((System.currentTimeMillis() - lastRetrieval) > (timeToLive * 1000)) : true;
}
private boolean isReloading() {
return cachedObject != null && isLoading;
}
public void setTimeToLive(Long timeToLive) {
this.timeToLive = timeToLive;
}
}
最佳答案
您编写的内容不是线程安全的。事实上,您无意中发现了一个常见的谬误,这是一个相当著名的问题。它被称为 double-checked locking problem许多像您这样的解决方案(以及这个主题有几个变体)都有问题。
对此有一些潜在的解决方案,但恕我直言,最简单的方法就是使用 ScheduledThreadExecutorService 并每分钟或您需要的频率重新加载您需要的内容。当您重新加载它时,将其放入缓存结果中,并且对它的调用只会返回最新版本。这是线程安全的并且易于实现。当然,它不是按需加载的,但是,除了初始值之外,您在检索该值时永远不会受到性能影响。我将其称为过度热切加载而不是延迟加载。
例如:
public class Cache<T> {
private final ScheduledExecutorsService executor =
Executors.newSingleThreadExecutorService();
private final Callable<T> method;
private final Runnable refresh;
private Future<T> result;
private final long ttl;
public Cache(Callable<T> method, long ttl) {
if (method == null) {
throw new NullPointerException("method cannot be null");
}
if (ttl <= 0) {
throw new IllegalArgumentException("ttl must be positive");
}
this.method = method;
this.ttl = ttl;
// initial hits may result in a delay until we've loaded
// the result once, after which there will never be another
// delay because we will only refresh with complete results
result = executor.submit(method);
// schedule the refresh process
refresh = new Runnable() {
public void run() {
Future<T> future = executor.submit(method);
future.get();
result = future;
executor.schedule(refresh, ttl, TimeUnit.MILLISECONDS);
}
}
executor.schedule(refresh, ttl, TimeUnit.MILLISECONDS);
}
public T getResult() {
return result.get();
}
}
这需要一点解释。基本上,您正在创建一个通用接口(interface)来缓存 Callable 的结果,这将是您的文档加载。提交 Callable(或 Runnable)会返回 Future。调用 Future.get() 会阻塞,直到它返回(完成)。
所以它的作用是根据 Future 实现 get() 方法,这样初始查询就不会失败(它们会阻塞)。之后,每隔“ttl”毫秒调用刷新方法。它将方法提交给调度程序并调用 Future.get(),该方法会产生结果并等待结果完成。一旦完成,它就会取代“结果”成员。后续 Cache.get() 调用将返回新值。
ScheduledExecutorService 上有一个 ScheduleWithFixedRate() 方法,但我避免使用它,因为如果 Callable 花费的时间超过预定的延迟,您将最终同时运行多个,然后必须担心该问题或限制。流程在刷新结束时提交自身会更容易。关于java - Java中的线程安全双缓冲缓存(不适用于图形)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1421615/
我是一名优秀的程序员,十分优秀!