caffeine: Java 8高性能缓存库包

Caffeine提供一个in-memory缓存,类似Google Guava的API,在Guava基础上提高了各种体验。

通常我们对缓存有两种操作,存入缓存和更新缓存:


Cache<K, V> cache = ...

public V read(K key) {
V result = cache.getIfPresent(key);
if (result == null) {
result = readFromDatabase(key);
cache.put(key, result);
}

return result;
}

public void write(K key, V value) {
writeToDatabase(key, value);
// force the value to be re-read from
// the database on next read(key) invocation
cache.invalidate(key);
};

这是一种get-if absent compute-put模式,也就是如果缓存没有该元素,则从数据库获得放入缓存。

假设有两个线程T1和T2,第一个调用缓存读取,第二个缓存写入,两个线程同时运行时,缓存中获取的值可能不是刚刚写入数据库值,也就是脏数据。

使用Guava测试代码如下:


AtomicReference<Integer> db = new AtomicReference<>(1);

LoadingCache<String, Integer> c = CacheBuilder.newBuilder().build(
new CacheLoader<String, Integer>() {
public Integer load(String key) {
println("Reading from db ...");
Integer v = db.get();
sleep(1000L);
println(
"Read from db: " + v);
return v;
}
}
);

Thread t1 = new Thread(() -> {
Integer g = c.get(
"k");
println(
"Got from cache: " + g);
});

Thread t2 = new Thread(() -> {
sleep(500L);
println(
"Writing to db ...");
db.set(2);
println(
"Wrote to db");
c.invalidate(
"k");
println(
"Invalidated cached");
});

t1.start();
t2.start();

t1.join();
t2.join();

System.out.println();
println(
"In cache: " + c.get("k"));

得到结果:


Thread-1 (123): Reading from db ...
Thread-2 (612): Writing to db ...
Thread-2 (612): Wrote to db
Thread-2 (613): Invalidated cached
Thread-1 (1142): Read from db: 1
Thread-1 (1145): Got from cache: 1

main (1146): In cache: 1

尽管缓存已经invalidate,但是缓存还是能获得K的值。

而如果使用Caffeine.newBuilder(),能得到正确结果:


Thread-1 (122): Reading from db ...
Thread-2 (624): Writing to db ...
Thread-2 (624): Wrote to db
Thread-1 (1146): Read from db: 1
Thread-2 (1146): Invalidated cached
Thread-1 (1146): Got from cache: 1

main (1147): Reading from db ...
main (2149): Read from db: 2
main (2149): In cache: 2

这获得了正确及俄国,是因为Caffeine 有一个 key-scoped锁,当调用cache.get时,指定键key的锁会一直保留到其值被计算加载完成。

GitHub - ben-manes/caffeine: A high performance ca