Background
I have a large data map (HashMap), kept in memory, which is updated incrementally (based on incoming messages), by the background thread:
<KEY> => <VALUE>
...
End users will then query it via the REST API:
GET /lookup?key=<KEY>
Updates are not applied immediately, but in batches, once a special control message is received, i.e.
MESSAGE: "Add A"
A=<VALUE> //Not visible yet
MESSAGE: "Add B"
B=<VALUE> //Not visible yet
MESSAGE: "Commit"
//Updates are now visible to the end-users
A=<VALUE>
B=<VALUE
The architecture I devised, is as follows:
volatile Map passiveCopy = new HashMap();
volatile Map activeCopy = new HashMap();
Map<String,Object> pendingUpdates;
//Interactive requests (REST API)
Object lookup(String key) {
activeCopy.get(key);
}
//Background thread processing the incoming messages.
//Messages are processed strictly sequentially
//i.e. no other message will be processed, until
//current handleMessage() invocation is completed
//(that is guaranteed by the message processing framework itself)
void handleMessage(Message msg) {
//New updates go to the pending updates temporary map
if(msg.type() == ADD) {
pendingUpdates.put(msg.getKey(),msg.getValue());
}
if(msg.type() == COMMIT) {
//Apply updates to the passive copy of the map
passiveCopy.addAll(pendingUpdates);
//Swap active and passive map copies
Map old = activeCopy;
activeCopy = passiveCopy;
passiveCopy = old;
//Grace period, wait for on-the-air requests to complete
//REST API has a hard timeout of 100ms, so no client
//will wait for the response longer than that
Thread.sleep(1000);
//Re-apply updates to the now-passive (ex-active) copy of the map
passiveCopy.addAll(pendingUpdates);
//Reset the pendingUpdates map
pendingUpdates.clear();
}
}
The question
Taking that write->read to the volatile field makes a happens-before edge:
A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5
and the grace period is chosen correctly, I expect that any updates applied to the passiveCopy (via putAll()), will become visible to the end-user requests (all at once), after the swap.
It is really a case, or there are any corner-case which will make this approach fail ?
NOTE
I know that creating a copy of the Map (so that a new Map instance is assigned to activeCopy an each time), would be safe to do, but I don't want to do this (as it is really large).