Skip to content

Instantly share code, notes, and snippets.

@theotherian
Last active September 7, 2018 19:46
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save theotherian/7478882 to your computer and use it in GitHub Desktop.
Save theotherian/7478882 to your computer and use it in GitHub Desktop.
Blocking on cache reads beyond the first is just plain rude in my opinion, so refresh in the background.
Blocking on cache reads beyond the first is just plain rude in my opinion...
package com.theotherian.cache;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
public class NonBlockingCache {
public static void main(String[] args) throws Exception {
final AtomicInteger value = new AtomicInteger(1);
// nicely named threads are nice
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("MyCacheRefresher-pool-%d").setDaemon(true).build();
ExecutorService parentExecutor = Executors.newSingleThreadExecutor(threadFactory);
// create an executor that provide ListenableFuture instances
final ListeningExecutorService executorService = MoreExecutors.listeningDecorator(parentExecutor);
final LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
.maximumSize(1)
// the cache will try to refresh any entry after it's first written if it's accessed more than 10 seconds
// after the last write
.refreshAfterWrite(10, TimeUnit.SECONDS)
.build(
new CacheLoader<String, Integer>() {
public Integer load(String key) throws Exception {
Thread.sleep(5000);
System.out.println("RELOADING!");
return value.getAndIncrement();
}
@Override
public ListenableFuture<Integer> reload(final String key, Integer oldValue) throws Exception {
// we need to load new values asynchronously, so that calls to read values from the cache don't block
ListenableFuture<Integer> listenableFuture = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("Async reload event");
return load(key);
}
});
return listenableFuture;
}
});
for (int i = 0; i < 100; i++) {
long start = System.currentTimeMillis();
Integer unchecked = cache.getUnchecked("foo");
System.out.printf("Took %dms to load result %d: %d\n", (System.currentTimeMillis() - start), (i + 1), unchecked);
Thread.sleep(1000);
}
}
}
@apelsina
Copy link

apelsina commented Nov 8, 2017

Hi! I found this gist extremely useful, thank you so much! It helped me a lot.

There's something I got curious about, though: why do you use a daemon thread here? Does it increase performance or is it just your own preference in threading?

I am new in threading. Thanx.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment