问题
I am populating my guava cache from multiple threads by calling add
method. Now from the background thread which runs every 30 seconds, I want to send whatever is there in the cache to sendToDB
method atomically?
Below is my code:
public class Example {
private final ScheduledExecutorService executorService = Executors
.newSingleThreadScheduledExecutor();
private final Cache<Integer, List<Process>> cache = CacheBuilder.newBuilder().maximumSize(100000)
.removalListener(RemovalListeners.asynchronous(new CustomRemovalListener(), executorService))
.build();
private static class Holder {
private static final Example INSTANCE = new Example();
}
public static Example getInstance() {
return Holder.INSTANCE;
}
private Example() {
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// is this the right way to send cache map?
sendToDB(cache.asMap());
}
}, 0, 30, SECONDS);
}
// this method will be called from multiple threads
public void add(final int id, final Process process) {
// add id and process into cache
}
// this will only be called from single background thread
private void sendToDB(ConcurrentMap<Integer, List<Process>> holder) {
// use holder here
}
}
Is this the right way to send cache
map to my sendToDB
method? Basically I want to send all the entries that are there in the cache for that 30 seconds and empty the cache out. After that my cache will get populated again in the next 30 seconds and then do the same process?
I think using cache.asMap()
might not be the right way since it doesn't empty the cache out so it will reflect all the changes happening on the cache in my sendToDB
method as well?
回答1:
How about:
@Override
public void run() {
ImmutableMap<Integer, List<Process>> snapshot = ImmutableMap.copyOf(cache.asMap());
cache.invalidateAll();
sendToDB(snapshot);
}
This will copy the contents of the cache into a new map, creating a snapshot of the cache at a particular point in time. Then .invalidateAll()
will empty the cache, after which the snapshot will be sent to the DB.
One disadvantage with this approach is it's racy - it's possible for entries to be added to the cache after the snapshot is created but before .invalidateAll()
is called, and such entries would never be sent to the DB. Since your cache could also evict entries due to the maximumSize()
setting I assume this isn't a concern, but if it is you'd want to instead remove the entry while constructing the snapshot, which would look like this:
@Override
public void run() {
Iterator<Entry<Integer, List<Process>> iter = cache.asMap().entrySet().iterator();
ImmutableMap<Integer, List<Process>> builder = ImmutableMap.builder();
while (iter.hasNext()) {
builder.add(iter.next());
iter.remove();
}
sendToDB(builder.build());
}
With this approach the cache
may not actually be empty when sendToDB()
is invoked, but every entry that was there before the snapshotting began will have been removed and will be sent to the database.
Alternatively you could create a wrapper class that has a Cache
field, and atomically swap the field out for a new empty cache, then copy the contents of the old cache to the database and allow it to be GCed.
来源:https://stackoverflow.com/questions/41777172/how-to-empty-guava-cache-every-30-seconds-while-sending-it-to-another-method