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