我下面的代码是否正确使用 Map 作为简单的线程安全缓存以避免从数据库读取?我只是想知道下面代码的正确性,而不是建议使用 X 框架。
public class Foo {
private static final Map<String, String> CACHE = new ConcurrentHashMap<>();
public void doWork(String key) {
String value = CACHE.get(key);
if (value == null) {
synchronized (CACHE) {
value = CACHE.get(key);
if (value == null) {
value = database.getValue();
CACHE.put(key, value);
}
}
}
// do work with value
}
}
其他问题:
- 如果我在类中有一个
对象锁
并在synchronized()
中使用CACHE
,而不是在synchronized()
中使用synchronized,会更好吗?而是那个? - 使用
HashMap
进行CACHE
可以吗?
最佳答案
以这种方式使用 ConcurrentHashMap 有一个相当标准的“模式”(在这种情况下,您不想要使用同步
> block 或其他锁定机制):
String value = CACHE.get(key);
if (value == null) {
/* 3 */ String newValue = calculateValueForKey(key);
/* 4 */ value = CACHE.putIfAbsent(key, newValue);
if (value == null) {
value = newValue;
}
}
/* Work with 'value' */
当 calculateValueForKey()
运行快速并且没有任何副作用时,此方法效果很好 - 根据时间的不同,可以对同一键多次调用它。缺点是,如果 calculateValueForKey()
需要很长时间并且受 I/O 限制(正如您的情况),您可能有多个线程都在运行 calculateValueForKey()
同时用于相同的 key 。如果有 3 个线程针对同一个键执行第 3 行,则其中 2 个线程将在第 4 行“失败”,并丢弃其结果,这不是很高效。对于这些情况,我会推荐一些类似的东西,这些东西主要是从 Java Concurrency in Practice 中的 Memoizer
示例中提取的。 (Goetz, B. (2006))我强烈推荐:
private static final ConcurrentMap<String, Future<String>> CACHE
= new ConcurrentHashMap<>();
public void doWork(String key)
{
String value;
try {
value = calculateValueForKey(key);
} catch (InterruptedException e) {
// Restore interrupted status and return
Thread.currentThread.interrupt();
return;
}
// do work with value
}
private String calculateValueForKey(final String key)
throws InterruptedException
{
while (true) {
Future<String> f = CACHE.get(key);
if (f == null) {
FutureTask<String> newCalc = new FutureTask<>(new Callable<String>() {
@Override
public String call()
{
return database.getValue(key);
}
)};
f = CACHE.putIfAbsent(key, newCalc);
if (f == null) {
f = newCalc;
newCalc.run();
}
}
try {
return f.get();
} catch (CancellationException e) {
CACHE.remove(key, f);
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
throw new IllegalStateException("Not unchecked", cause);
}
}
}
}
显然这段代码更复杂,这就是为什么我将其核心内容提取到另一个方法中,但它非常强大。您不是将值放入映射中,而是将表示该值的计算的 Future
放入映射中。在该 future 上调用 get() 将会阻塞,直到计算完成。这意味着,如果 3 个线程同时尝试检索给定键的值,则当所有 3 个线程等待相同结果时,只会运行一次计算。对同一 key 的后续请求将立即返回计算结果。
回答您的具体问题:
- 我下面的代码使用 Map 作为简单的线程安全缓存以避免从数据库读取数据是否正确?我会说不。您在这里使用
synchronized
block 是不必要的。此外,如果多个线程同时尝试访问 Map 中尚未存在的不同键的值,它们将在各自的数据库查询期间相互阻塞,这意味着它们将串行运行而不是顺序运行。平行。 - 如果我在类中使用对象锁并对其使用同步,而不是在
synchronized()
中使用 CACHE,会更好吗? 不。当您想要读/写多个可变字段并且不希望类的使用者能够“从外部”影响对象的同步语义时,您通常会使用代理对象进行同步。 ” - 使用
HashMap
作为CACHE
可以吗?我猜你可以吗?但是,您需要调整同步策略,以便在读取Map
时,CACHE
(或代理锁对象)始终同步
或写信给.我不确定如果有更好的选择,您为什么要这样做。
关于java - 在 ConcurrentHashmap 中缓存值以避免数据库读取,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38614178/