上一篇介紹了guava的使用,實現了項目第一個需求定期清理cache數據,第二個需求,我們需要在緩存被移除的時候,得到通知產生回調,並做一些額外處理工作。這個時候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) {
-
-
-
Cache<Integer, Integer> cache = CacheBuilder.newBuilder().removalListener(new MyRemovalListener()).build();
-
-
cache.put(1, 1);
-
-
-
cache.invalidate(1);
-
-
System.out.println(cache.getIfPresent(1));
-
}
-
-
}
使用invalidate()清除緩存數據之後,註冊的回調被觸發了。
下面是隻有主動刪除數據使的回調
public class CacheConnection {
public static RemovalListener<String, Connection> myRemovalListener = new RemovalListener<String, Connection>(){
@Override
public void onRemoval(RemovalNotification<String, Connection> notification) {
String tips = String.format("key=%s,value=%s,reason=%s in myRemovalListener", notification.getKey(), notification.getValue(), notification.getCause());
System.out.println(tips);
//when expireAfterAccess to do
if (notification.getCause().equals("EXPIRED") && notification.getValue() != null) {
try {
notification.getValue().close();
} catch (SQLException e) {
System.out.printf("Exception in myRemovalListener:\n");
e.printStackTrace();
}
System.out.printf("Remove %s in cacheConnection", notification.getKey());
}
}
};
public static Cache<String, Connection> cacheConnection = CacheBuilder.newBuilder()
//設置cache的初始大小爲20000,要合理設置該值
.initialCapacity(20000)
//設置併發數爲5,即同一時間最多隻能有5個線程往cache執行寫入操作
.concurrencyLevel(100)
//設置cache中的數據在600秒沒有被讀寫將自動刪除
.expireAfterAccess(600, TimeUnit.SECONDS)
//設置監聽,當出現自動刪除時的回調
.removalListener(myRemovalListener)
//構建cache實例
.build();
public static Connection getCache(String key) {
try {
Connection var = cacheConnection.getIfPresent(key);
return var;
} catch (Exception e) {
// TODO: handle exception
System.out.println("the value of cacheConnection is null");
e.printStackTrace();
return null;
}
}
public static void putCache(String key, Connection value) {
cacheConnection.put(key, value);
}
}
RemovalNotification中包含了緩存的key、value以及被移除的原因RemovalCause。通過源碼可以看出,移除原因與容量管理方式是相對應的。下面是具體的消息
-
public enum RemovalCause {
-
-
-
-
-
-
EXPLICIT {
-
@Override
-
boolean wasEvicted() {
-
return false;
-
}
-
},
-
-
-
-
-
-
-
-
REPLACED {
-
@Override
-
boolean wasEvicted() {
-
return false;
-
}
-
},
-
-
-
-
-
-
-
COLLECTED {
-
@Override
-
boolean wasEvicted() {
-
return true;
-
}
-
},
-
-
-
-
-
-
EXPIRED {
-
@Override
-
boolean wasEvicted() {
-
return true;
-
}
-
},
-
-
-
-
-
-
SIZE {
-
@Override
-
boolean wasEvicted() {
-
return true;
-
}
-
};
-
-
-
-
-
-
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) {
-
-
-
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);
-
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());
-
-
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);
-
System.out.println("main...over.");
-
}
-
-
}
2、創建cache的時候只能添加1個監聽器,這個監聽器對象會被多個線程共享,所以如果監聽器需要操作共享資源,那麼一定要做好同步控制。下面這段代碼可以看出:2個線程會交替執行監聽器的發方法。
-
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) {
-
-
-
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();
-
}
-
-
}
3、監聽器中拋出的任何異常,在被記錄到日誌後,會被guava丟棄,不會導致監聽器不可用。下面這段代碼可以看到:監聽器中拋出的異常只是被記錄了(打印到了控制檯),並沒有導致JVM退出,之後緩存被移除一樣可以再次觸發。
-
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) {
-
-
-
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);
-
}
-
-
}