天天看點

Guava1.2——GuavaCache的RemoveListener分析

put操作

1.prewrite

void preWriteCleanup(long now)

void runLockedCleanup(long now)

void drainReferenceQueues()

void expireEntries(long now)

以上是準備工作,和get方法中的準備類似。

2.postwrite

void postWriteCleanup()

void runUnlockedCleanup();

// 該方法會把removalNotificationQueue中的元素清楚掉,并調用onRemoval方法

void processPendingNotifications(){

RemovalNotification<K, V> notification;

while ((notification = removalNotificationQueue.poll()) != null) {

try {

removalListener.onRemoval(notification);

} catch (Throwable e) {

logger.log(Level.WARNING, "Exception thrown by removal listener", e);

}

}

}

是以真正調用OnRemoval方法是在put操作中調用的。

get操作

1.pre read

其實不像沒有pre read方法,隻是為了對應pre write。

V get(Object key, int hash)

ReferenceEntry<K, V> getLiveEntry(Object key, int hash, long now)

void tryExpireEntries(long now)

void expireEntries(long now) {

drainRecencyQueue();

ReferenceEntry<K, V> e;

while ((e = writeQueue.peek()) != null && map.isExpired(e, now)) {

if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {

throw new AssertionError();

}

}

while ((e = accessQueue.peek()) != null && map.isExpired(e, now)) {

if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {

throw new AssertionError();

}

}

}

boolean removeEntry(ReferenceEntry<K, V> entry, int hash, RemovalCause cause) ;

ReferenceEntry<K, V> removeValueFromChain(ReferenceEntry<K, V> first,

ReferenceEntry<K, V> entry, @Nullable K key, int hash, ValueReference<K, V> valueReference,

RemovalCause cause) {

enqueueNotification(key, hash, valueReference, cause);

writeQueue.remove(entry);

accessQueue.remove(entry);

if (valueReference.isLoading()) {

valueReference.notifyNewValue(null);

return first;

} else {

return removeEntryFromChain(first, entry);

}

}

ReferenceEntry<K, V> removeValueFromChain(ReferenceEntry<K, V> first,

ReferenceEntry<K, V> entry, @Nullable K key, int hash, ValueReference<K, V> valueReference,

RemovalCause cause) {

enqueueNotification(key, hash, valueReference, cause);

writeQueue.remove(entry);

accessQueue.remove(entry);

if (valueReference.isLoading()) {

valueReference.notifyNewValue(null);

return first;

} else {

return removeEntryFromChain(first, entry);

}

}

// 向removalNotificationQueue隊列中插入元素,在調用OnRemoval的時候會删除這些元素

void enqueueNotification(@Nullable K key, int hash, ValueReference<K, V> valueReference,

RemovalCause cause) {

totalWeight -= valueReference.getWeight();

if (cause.wasEvicted()) {

statsCounter.recordEviction();

}

if (map.removalNotificationQueue != DISCARDING_QUEUE) {

V value = valueReference.get();

RemovalNotification<K, V> notification = new RemovalNotification<K, V>(key, value, cause);

map.removalNotificationQueue.offer(notification);

}

}

2.post read

void postReadCleanup() {

if ((readCount.incrementAndGet() & DRAIN_THRESHOLD) == 0) {

cleanUp();

}

}

void cleanUp() {

long now = map.ticker.read();

runLockedCleanup(now);

runUnlockedCleanup();

}

3.解釋

其實,在get方法調用的時候,已經過期的value已經取不到了,但是要是想讓OnRemoval響應,還是要等下一次put才能生效,原因見put方法解釋。

要涉及鍵值的讀操作,都将執行postReadCleanup操作,每次執行postReadCleanup操作時readCount都增1,當其達到64時(DRAIN_THRESHOLD為0x3F,即0011 1111),引發cleanUp操作,也會調用OnRemoval方法。

也就是說postRead和postWrite會操作OnRemoval方法。

示例

Cache<String, Integer> cache = CacheBuilder.newBuilder()

.maximumSize(10000)

.expireAfterWrite(2, TimeUnit.SECONDS)

.recordStats()

.removalListener(new RemovalListener<String, Integer>() {

@Override

public void onRemoval(RemovalNotification<String, Integer> rn) {

System.out.println("被移除的key:" + rn.getKey() + ", 原因:" + rn.getCause());

}

})

.build();

cache.put("a", 100);

Thread.sleep(4000);

cache.getIfPresent("a");

cache.put("c", 300);

如果希望OnRemoval方法被調用,那麼紅色的兩行缺一不可。首先get方法會将已經過期的a-100鍵值對塞入删除隊列,即queue的offer操作,然後put方法會觸發queue的poll操作,如果取出來元素的話,就執行OnRemoval操作

while ((notification = removalNotificationQueue.poll()) != null) {

try {

removalListener.onRemoval(notification);

...

繼續閱讀