之前文章已經介紹了guava的容量管理,有4種方式可以將數據從緩存中移除。有的時候,我們需要在緩存被移除的時候,得到這個通知,並做一些額外處理工作。這個時候RemovalListener就派上用場了。
public class Main {
// 創建一個監聽器
private static class MyRemovalListener implements RemovalListener<Integer, Integer> {
@Override
public void onRemoval(RemovalNotification<Integer, Integer> notification) {
String tips = String.format("key=%s,value=%s,reason=%s", notification.getKey(), notification.getValue(), notification.getCause());
System.out.println(tips);
}
}
public static void main(String[] args) {
// 創建一個帶有RemovalListener監聽的緩存
Cache<Integer, Integer> cache = CacheBuilder.newBuilder().removalListener(new MyRemovalListener()).build();
cache.put(1, 1);
// 手動清除
cache.invalidate(1);
System.out.println(cache.getIfPresent(1)); // null
}
}
使用invalidate()清除緩存數據之後,註冊的回調被觸發了。
RemovalNotification中包含了緩存的key、value以及被移除的原因RemovalCause。通過源碼可以看出,移除原因與容量管理方式是相對應的。
public enum RemovalCause {
/**
* The entry was manually removed by the user. This can result from the user invoking
* {@link Cache#invalidate}, {@link Cache#invalidateAll(Iterable)}, {@link Cache#invalidateAll()},
* {@link Map#remove}, {@link ConcurrentMap#remove}, or {@link Iterator#remove}.
*/
EXPLICIT {
@Override
boolean wasEvicted() {
return false;
}
},
/**
* The entry itself was not actually removed, but its value was replaced by the user. This can
* result from the user invoking {@link Cache#put}, {@link LoadingCache#refresh}, {@link Map#put},
* {@link Map#putAll}, {@link ConcurrentMap#replace(Object, Object)}, or
* {@link ConcurrentMap#replace(Object, Object, Object)}.
*/
REPLACED {
@Override
boolean wasEvicted() {
return false;
}
},
/**
* The entry was removed automatically because its key or value was garbage-collected. This
* can occur when using {@link CacheBuilder#weakKeys}, {@link CacheBuilder#weakValues}, or
* {@link CacheBuilder#softValues}.
*/
COLLECTED {
@Override
boolean wasEvicted() {
return true;
}
},
/**
* The entry's expiration timestamp has passed. This can occur when using
* {@link CacheBuilder#expireAfterWrite} or {@link CacheBuilder#expireAfterAccess}.
*/
EXPIRED {
@Override
boolean wasEvicted() {
return true;
}
},
/**
* The entry was evicted due to size constraints. This can occur when using
* {@link CacheBuilder#maximumSize} or {@link CacheBuilder#maximumWeight}.
*/
SIZE {
@Override
boolean wasEvicted() {
return true;
}
};
/**
* Returns {@code true} if there was an automatic removal due to eviction (the cause is neither
* {@link #EXPLICIT} nor {@link #REPLACED}).
*/
abstract boolean wasEvicted();
}
監聽器使用很簡單,有幾個特點需要注意下:
1、默認情況下,監聽器方法是被同步調用的(在移除緩存的那個線程中執行)。如果監聽器方法比較耗時,會導致調用者線程阻塞時間變長。下面這段代碼,由於監聽器執行需要2s,所以main線程調用invalidate()要2s後才能返回。
public class Main {
// 創建一個監聽器
private static class MyRemovalListener implements RemovalListener<Integer, Integer> {
@Override
public void onRemoval(RemovalNotification<Integer, Integer> notification) {
String tips = String.format("key=%s,value=%s,reason=%s", notification.getKey(), notification.getValue(), notification.getCause());
System.out.println(tips);
try {
// 模擬耗時
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 創建一個帶有RemovalListener監聽的緩存
final Cache<Integer, Integer> cache = CacheBuilder.newBuilder().removalListener(new MyRemovalListener()).build();
cache.put(1, 1);
cache.put(2, 2);
System.out.println("main...begin.");
cache.invalidate(1);// 耗時2s
System.out.println("main...over.");
}
}
解決這個問題的方法是:使用異步監聽RemovalListeners.asynchronous(RemovalListener, Executor)。
public class Main {
// 創建一個監聽器
private static class MyRemovalListener implements RemovalListener<Integer, Integer> {
@Override
public void onRemoval(RemovalNotification<Integer, Integer> notification) {
String tips = String.format("key=%s,value=%s,reason=%s", notification.getKey(), notification.getValue(), notification.getCause());
System.out.println(tips);
try {
// 模擬耗時
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
RemovalListener<Integer, Integer> async = RemovalListeners.asynchronous(new MyRemovalListener(), Executors.newSingleThreadExecutor());
// 創建一個帶有RemovalListener監聽的緩存
final Cache<Integer, Integer> cache = CacheBuilder.newBuilder().removalListener(async).build();
cache.put(1, 1);
cache.put(2, 2);
System.out.println("main...begin.");
cache.invalidate(1);// main線程立刻返回
System.out.println("main...over.");
}
}
public class Main {
// 創建一個監聽器
private static class MyRemovalListener implements RemovalListener<Integer, Integer> {
@Override
public void onRemoval(RemovalNotification<Integer, Integer> notification) {
String tips = String.format("key=%s,value=%s,reason=%s", notification.getKey(), notification.getValue(), notification.getCause());
System.out.println(tips);
try {
// 模擬耗時
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("process over.");
}
}
public static void main(String[] args) {
// 創建一個帶有RemovalListener監聽的緩存
final Cache<Integer, Integer> cache = CacheBuilder.newBuilder().removalListener(new MyRemovalListener()).build();
cache.put(1, 1);
cache.put(2, 2);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread1...trigger RemovalListener begin.");
cache.invalidate(1);
System.out.println("thread1...trigger RemovalListener over.");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread2...trigger RemovalListener begin.");
cache.invalidate(2);
System.out.println("thread2...trigger RemovalListener over.");
}
}).start();
}
}
public class Main {
// 創建一個監聽器
private static class MyRemovalListener implements RemovalListener<Integer, Integer> {
@Override
public void onRemoval(RemovalNotification<Integer, Integer> notification) {
String tips = String.format("key=%s,value=%s,reason=%s", notification.getKey(), notification.getValue(), notification.getCause());
System.out.println(tips);
throw new RuntimeException();
}
}
public static void main(String[] args) {
// 創建一個帶有RemovalListener監聽的緩存
final Cache<Integer, Integer> cache = CacheBuilder.newBuilder().removalListener(new MyRemovalListener()).build();
cache.put(1, 1);
cache.put(2, 2);
cache.invalidate(1);
cache.invalidate(2);
}
}