我想要一个Java集合:
Object
映射到Object
s(不是String
或其他受限制的密钥)如果在多个线程中同时缓存未命中导致冗余计算,则可以。典型的情况是缓存最初主要由一个线程填充。
线程不安全哈希表周围的synchronized
块无法满足高效读取标准。线程局部缓存很简单,但意味着新线程很昂贵,因为它们有完整的缓存副本。
我们可以将Java 1.5内置函数或一个或多个类文件复制到我们的MIT许可项目中,而不是大型外部库。
答案 0 :(得分:7)
使用java并发hashmap
ConcurrentHashMap<object, object> table;
public object getFromCache(object key)
{
value = table.get(key);
if (value == null)
{
//key isn't a key into this table, ie. it's not in the cache
value = calculateValueForKey(key)
object fromCache = table.putIfAbsent(key, value);
}
return value;
}
/**
* This calculates a new value to put into the cache
*/
public abstract object calculateValueForKey(object key);
N.b。这不再是多线程缓存的一般解决方案,因为它依赖于对象是不可变的所述事实,因此对象等价并不重要。
答案 1 :(得分:2)
这是我自己的解决方案的想法,但我不是线程编程方面的专家,所以请在适当的时候评论/投票/比较其他答案。
使用线程局部变量(java.lang.ThreadLocal),其中包含用作第一级缓存的每线程哈希表。如果在此表中找不到密钥,则对第二级高速缓存进行同步访问,该高速缓存是所有线程共享的synchronized
- 访问哈希表。通过这种方式,缓存值的计算只进行一次,并且它在所有线程之间共享,但每个线程都有从键到值的映射的本地副本,因此存在一些内存成本(但低于独立的每线程缓存),但读取是有效的。
答案 2 :(得分:2)
我的一个项目中有关SingletonCache类的内容怎么样?
public abstract class SingletonCache<K, V> {
private final ConcurrentMap<K, V> cache = new ConcurrentHashMap<K, V>();
public V get(K key) {
V value = cache.get(key);
if (value == null) {
cache.putIfAbsent(key, newInstance(key));
value = cache.get(key);
}
return value;
}
protected abstract V newInstance(K key);
}
要使用它,您可以扩展它并实现newInstance
方法,该方法在高速缓存未命中时创建新值。然后使用键调用get
方法以获取与该键对应的实例。 Here是如何使用它的一个例子。
该类保证对于每个键只返回一个实例,但可以多次调用newInstance
方法,在这种情况下使用第一个计算实例,其余的被丢弃。另请注意,此缓存不会删除旧实例,但会无限期地存储所有值(在我的用例中,必须缓存的实例数量有限)。从ConcurrentHashMap读取不使用锁定,因此它应该满足您的效率要求。
答案 3 :(得分:1)
Concurrency In Practice的Brian Goetz第5.6节中描述的缓存怎么样?概述了here。
它只使用java.util.concurrent包中的类。
链接文章构建了一个缓存并描述了每个版本的弱点,直到最终版本是一个有效的缓存,其中只有一个并发线程将计算缺席条目。
我已经剪切并粘贴了下面的最终代码,但是值得一读文章并思考所概述的问题。甚至更好 - 买书 - 这很棒。
import java.util.concurrent.*;
public class Memoizer<A, V> implements Computable<A, V> {
private final ConcurrentMap<A, Future<V>> cache
= new ConcurrentHashMap<A, Future<V>>();
private final Computable<A, V> c;
public Memoizer(Computable<A, V> c) { this.c = c; }
public V compute(final A arg) throws InterruptedException {
while (true) {
Future<V> f = cache.get(arg);
if (f == null) {
Callable<V> eval = new Callable<V>() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<V>(eval);
f = cache.putIfAbsent(arg, ft);
if (f == null) {
f = ft;
ft.run();
}
}
try {
return f.get();
} catch (CancellationException e) {
cache.remove(arg, f);
} catch (ExecutionException e) {
// Kabutz: this is my addition to the code...
try {
throw e.getCause();
} catch (RuntimeException ex) {
throw ex;
} catch (Error ex) {
throw ex;
} catch (Throwable t) {
throw new IllegalStateException("Not unchecked", t);
}
}
}
}
}