2
votes

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
asMap returns a copy of the content if I remember well, just 1/ asMap, clean, then call your methoduser180100
@RC. No, it's just a view.shmosel
Can you fill in the add() method (at least an outline)? Are you using Java 8? Do you have to clear the map, or could you leave the keys and reset the values?shmosel
I am working with Java7. Cannot use Java 8. I need to clear the map. Meaning whatever is there in that 30 seconds, send it to that method (which will process these records and send) and start populating fresh in that cache.john

1 Answers

0
votes

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.