`
raging_sweet
  • 浏览: 58978 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Correct use of ConcurrentHashMap

 
阅读更多

ConcurrentHashMap has been pitched as a simple alternative for HashMap, eliminating the need for a synchronized blocks. I had some simple event counting code that created count records on the fly. Although I could have used synchronized blocks for safety I used ConcurrentHashMap for this situation, partly for efficiency but mostly for the exercise. Going through this made me realize how carefully ConcurrentHashMap must be used for your code to work correctly and efficiently.

When using a HashMap, the standard idiom to add a value if it doesn’t exist is to use code that looks something like this:

synchronized (this) {
  Record rec = records.get(id);
  if (rec == null) {
      rec = new Record(id);
      records.put(id, rec);
  }
  return rec;
}

If you were to simply replace HashMap with ConcurrentHashMap and remove the synchronized keyword your code would be exposed to a race condition. If a new Record was put into the map just after the call to getreturned null the put operation would overwrite the value. You could addsynchronized back in but this defeats the purpose of using ConcurrentHashMap.

To safely create values on demand you must use putIfAbsent (and avoid making extra calls to get in the process).

First check to see if a value with the key already exists in the map and use this value if it does. Otherwise, create a new value for the map and add it with putIfAbsentputIfAbsent returns any existing value if there is one, otherwise null (this is why ConcurrentHashMap can’t contain null values).

private ConcurrentMap<String, Record> records =
     new ConcurrentHashMap<String, Record>();

private Record getOrCreate(String id) {
    Record rec = records.get(id);
    if (rec == null) {
        // record does not yet exist
        Record newRec = new Record(id);
        rec = records.putIfAbsent(id, newRec);
        if (rec == null) {
            // put succeeded, use new value
            rec = newRec;
        }
    }
    return rec;
}

If putIfAbsent does return a value, it’s the one that must be used. It may have already been used by other threads at this point. The new value created must be abandoned. Although it sounds wasteful this case should happen very infrequently.

I’ve seen other code on the net that ignores the return value ofputIfAbsent and makes another call to get at the end to figure out which value made it into the map (the new value created or a value from another thread). Although this will work it introduces an unnecessary lookup.

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics