I have what I thought was a simple use of Guava cache. However, the behavior is not intuitive to me. I have a POJO, Foo
with attribute Id
(Integer
). I use the Integer
as the key to the cache when retrieving instances of Foo
. If I put three items in the cache, and sleep for a period long enough to have everything expire, I would expect the same behavior regardless of the key value. The problem is that I see different behavior based on the key used. I get three objects into the cache: 1000, 2000, and 3000.
[main] INFO CacheTestCase - 3000 creating foo, 1000
[main] INFO CacheTestCase - 3000 creating foo, 2000
[main] INFO CacheTestCase - 3000 creating foo, 3000
[main] INFO CacheTestCase - 3000 Sleeping to let some cache expire . . .
[main] INFO CacheTestCase - 3000 Continuing . . .
[main] INFO CacheTestCase - 3000 Removed, 1000
[main] INFO CacheTestCase - 3000 Removed, 2000
[main] INFO CacheTestCase - 3000 creating foo, 1000
[main] INFO CacheTestCase -
Notice that, in the above run, the instance of Foo with a key of 3000 was not removed from the cache. Below is the output for the same code, but instead of a key of 3000, I used 4000.
[main] INFO CacheTestCase - 4000 creating foo, 1000
[main] INFO CacheTestCase - 4000 creating foo, 2000
[main] INFO CacheTestCase - 4000 creating foo, 4000
[main] INFO CacheTestCase - 4000 Sleeping to let some cache expire . . .
[main] INFO CacheTestCase - 4000 Continuing . . .
[main] INFO CacheTestCase - 4000 Removed, 1000
[main] INFO CacheTestCase - 4000 Removed, 2000
[main] INFO CacheTestCase - 4000 Removed, 4000
[main] INFO CacheTestCase - 4000 creating foo, 1000
Surely, I've done something incredibly stupid. Here's my MCVE:
package org.dlm.guava;
import com.google.common.cache.*;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;
/**
* Created by dmcreynolds on 8/17/2015.
*/
public class CacheTestCase {
static final Logger log = LoggerFactory.getLogger("CacheTestCase");
String p = ""; // just to make the log messages different
int DELAY = 10000; // ms
@Test
public void testCache123() throws Exception {
p = "3000";
LoadingCache<Integer, Foo> fooCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(100, TimeUnit.MILLISECONDS)
.removalListener(new FooRemovalListener())
.build(
new CacheLoader<Integer, Foo>() {
public Foo load(Integer key) throws Exception {
return createExpensiveFoo(key);
}
});
fooCache.get(1000);
fooCache.get(2000);
fooCache.get(3000);
log.info(p + " Sleeping to let some cache expire . . .");
Thread.sleep(DELAY);
log.info(p + " Continuing . . .");
fooCache.get(1000);
}
private Foo createExpensiveFoo(Integer key) {
log.info(p+" creating foo, " + key);
return new Foo(key);
}
public class FooRemovalListener
implements RemovalListener<Integer, Foo> {
public void onRemoval(RemovalNotification<Integer, Foo> removal) {
removal.getCause();
log.info(p+" Removed, " + removal.getKey().hashCode());
}
}
/**
* POJO Foo
*/
public class Foo {
private Integer id;
public Foo(Integer newVal) {
this.id = newVal;
}
public Integer getId() {
return id;
}
public void setId(Integer newVal) {
this.id = newVal;
}
}
}