开发者

How to use ReadWriteLock?

开发者 https://www.devze.com 2022-12-10 13:30 出处:网络
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

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 ReadWrit开发者_运维知识库eLock 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


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.


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.


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 ;)


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


This is the answer from the JDK javadocs for ReentrantReadWriteLock implementation of ReadWriteLock. A few years late but still valid, especially if you don't want to rely only on volatile

class RWDictionary {
    private final Map<String, Data> m = new TreeMap<String, Data>();
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public Data get(String key) {
        r.lock();
        try { return m.get(key); }
        finally { r.unlock(); }
    }
    public String[] allKeys() {
        r.lock();
        try { return m.keySet().toArray(); }
        finally { r.unlock(); }
    }
    public Data put(String key, Data value) {
        w.lock();
        try { return m.put(key, value); }
        finally { w.unlock(); }
    }
    public void clear() {
        w.lock();
        try { m.clear(); }
        finally { w.unlock(); }
    }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号