tags:

views:

246

answers:

4

Hello,

I'm the following situation.

At web application startup I need to load a Map which is thereafter used by multiple incoming threads. That is, requests comes in and the Map is used to find out whether it contains a particular key and if so the value (the object) is retrieved and associated to another object.

Now, at times the content of the Map changes. I don't want to restart my application to reload the new situation. Instead I want to do this dynamically.

However, at the time the Map is re-loading (removing all items and replacing them with the new ones), concurrent read requests on that Map still arrive.

What should I do to prevent all read threads from accessing that Map while it's being reloaded ? How can I do this in the most performant way, because I only need this when the Map is reloading which will only occur sporadically (each every x weeks) ?

If the above is not an option (blocking) how can I make sure that while reloading my read request won't suffer from unexpected exceptions (because a key is no longer there, or a value is no longer present or being reloaded) ?

I was given the advice that a ReadWriteLock might help me out. Can you someone provide me an example on how I should use this ReadWriteLock with my readers and my writer ?

Thanks,
E

+5  A: 

I suggest to handle this as follow:

  1. Have your map accessible at a central place (could be a Spring singleton, a static ...).
  2. When starting to reload, let the instance as is, work in a different Map instance.
  3. When that new map is filled, replace the old map with this new one (that's an atomic operation).

Sample code:

    static volatile Map<U, V> map = ....;

    // **************************

    Map<U, V> tempMap = new ...;
    load(tempMap);
    map = tempMap;

Concurrency effects :

  • volatile helps with visibility of the variable to other threads.
  • While reloading the map, all other threads see the old value undisturbed, so they suffer no penalty whatsoever.
  • Any thread that retrieves the map the instant before it is changed will work with the old values.
    • It can ask several gets to the same old map instance, which is great for data consistency (not loading the first value from the older map, and others from the newer).
    • It will finish processing its request with the old map, but the next request will ask the map again, and will receive the newer values.
KLE
+3  A: 

If the client threads do not modify the map, i.e. the contents of the map is solely dependent on the source from where it is loaded, you can simply load a new map and replace the reference to the map your client threads are using once the new map is loaded.

Other then using twice the memory for a short time, no performance penalty is incurred.

In case the map uses too much memory to have 2 of them, you can use the same tactic per object in the map; iterate over the map, construct a new mapped-to object and replace the original mapping once the object is loaded.

rsp
Doing this on a per object basis will leave the map in a mixed state, though---some objects will be from the old map, some from the new. Whether that matters depends on the use case.
uckelman
+2  A: 

Note that changing the reference as suggested by others could cause problems if you rely on the map being unchanged for a while (e.g. if (map.contains(key)) {V value = map.get(key); ...}. If you need that, you should keep a local reference to the map:

static Map<U,V> map = ...;

void do() {
  Map<U,V> local = map;
  if (local.contains(key)) {
    V value = local.get(key);
    ...
  }
}

EDIT:

The assumption is that you don't want costly synchronization for your client threads. As a trade-off, you allow client threads to finish their work that they've already begun before your map changed - ignoring any changes to the map that happened while it is running. This way, you can safely made some assumptions about your map - e.g. that a key is present and always mapped to the same value for the duration of a single request. In the example above, if your reader thread changed the map just after a client called map.contains(key), the client might get null on map.get(key) - and you'd almost certainly end this request with a NullPointerException. So if you're doing multiple reads to the map and need to do some assumptions as the one mentioned before, it's easiest to keep a local reference to the (maybe obsolete) map.

The volatile keyword isn't strictly necessary here. It would just make sure that the new map is used by other threads as soon as you changed the reference (map = newMap). Without volatile, a subsequent read (local = map) could still return the old reference for some time (we're talking about less than a nanosecond though) - especially on multicore systems if I remember correctly. I wouldn't care about it, but f you feel a need for that extra bit of multi-threading beauty, your free to use it of course ;)

sfussenegger
Do you mean static <b>volatile Map<U,V> map = ..;</b> or doesn't that make a difference ?So when my mapreloader (other thread) does map = "newly created map" while my reader thread is accessing a local reference of that map, my reader is unaware of that and keeps accessing the "old" map until it performs local = map ? Is that correct ?
EdwinDhondt
see my edit above
sfussenegger
But in both local = map approaches (with or without volatile) I won't ever get nullpointerexceptions, only perhaps some stale data ? Is that right ?
EdwinDhondt
That's right. Regarding `volatile`, here are two good resource: http://www.javamex.com/tutorials/synchronization_volatile.shtml and http://www.javamex.com/tutorials/synchronization_volatile_when.shtml
sfussenegger
If my reader thread gets an object from the map (getObject(key)) and after the get the (volatile) map is "replaced" with the new one, I suppose my reader thread still has a valid reference to the object taken from the "old" map ?
EdwinDhondt
In Java, you will always have a "valid reference", no matter what you do. So you will also have a "valid reference" to the old map itself. As a result the old map will be available for garbage collection as soon as all requests using it have finished (i.e. as soon as no references to it remain).
sfussenegger
+2  A: 

I like the volatile Map solution from KLE a lot and would go with that. Another idea that someone might find interesting is to use the map equivalent of a CopyOnWriteArrayList, basically a CopyOnWriteMap. We built one of these internally and it is non-trivial but you might be able to find a COWMap out in the wild:

http://old.nabble.com/CopyOnWriteMap-implementation-td13018855.html

Alex Miller